Search code examples
flutterloadingappbar

Using json file to give appbar logo for flutter. Facing Loading issue while navigation


I wanted to show appbar logo for all the pages from a json file. Now the issue is if I use Futurebuilder then in every page load, appbar logo awaits before showing. I tried to use shared preference but having issues. Maybe I am doing it wrong. I am a newbie in flutter. If possible please give the answer in simple way so I can understand. Or anyone can help me by creating that part for appbar. That will be helpful.

Here is my json file for logo

import 'dart:convert';
import 'package:models/app_logo.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './home_screen.dart';
import './login_screen.dart';
import '../constants.dart';
import '../screens/courses_screen.dart';
import '../screens/my_courses_screen.dart';
import '../screens/my_wishlist_screen.dart';
import '../screens/account_screen.dart';
import '../widgets/filter_widget.dart';
import '../providers/auth.dart';
import 'package:http/http.dart' as http;

class TabsScreen extends StatefulWidget {
  @override
  _TabsScreenState createState() => _TabsScreenState();
}

class _TabsScreenState extends State<TabsScreen> {
  List<Widget> _pages = [
    HomeScreen(),
    LoginScreen(),
    LoginScreen(),
    LoginScreen(),
  ];
  var _isInit = true;
  var _isLoading = false;

  int _selectedPageIndex = 0;
  bool _isSearching = false;
  final searchController = TextEditingController();

  Future<AppLogo> futureLogo;

  Future<AppLogo> fetchMyLogo() async {
    var url = BASE_URL + '/app_logo';
    try {
      final response = await http.get(url);
      print(response.body);
      if (response.statusCode == 200) {
        // If the server did return a 200 OK response,
        // then parse the JSON.
        print(response.body);
        return AppLogo.fromJson(jsonDecode(response.body));
      }
      // print(extractedData);
    } catch (error) {
      throw (error);
    }
  }

  @override
  void initState() {
    super.initState();
    this.fetchMyLogo();
    // Provider.of<Auth>(context).tryAutoLogin().then((_) {});
  }

  @override
  void didChangeDependencies() {
    if (_isInit) {
      setState(() {
        _isLoading = true;
      });

      final _isAuth = Provider.of<Auth>(context, listen: false).isAuth;

      if (_isAuth) {
        _pages = [
          HomeScreen(),
          MyCoursesScreen(),
          MyWishlistScreen(),
          AccountScreen(),
        ];
      }
    }
    _isInit = false;
    super.didChangeDependencies();
  }

  void _handleSubmitted(String value) {
    final searchText = searchController.text;
    if (searchText.isEmpty) {
      return;
    }

    searchController.clear();
    Navigator.of(context).pushNamed(
      CoursesScreen.routeName,
      arguments: {
        'category_id': null,
        'seacrh_query': searchText,
        'type': CoursesPageData.Search,
      },
    );
    // print(searchText);
  }

  void _selectPage(int index) {
    setState(() {
      _selectedPageIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        iconTheme: IconThemeData(
          color: kSecondaryColor, //change your color here
        ),
        title: !_isSearching
            ? FutureBuilder<AppLogo>(
          future: fetchMyLogo(),
          builder: (context, snapshot) {
            if (snapshot.connectionState ==
                ConnectionState.waiting) {
              return Center(
                child: Container(),
              );
            } else {
              if (snapshot.error != null) {
                return Center(
                  child: Text("Error Occured"),
                );
              } else {
                return Image.network(
                  snapshot.data.darkLogo,
                  fit: BoxFit.contain,
                  height: 27,
                );
              }
            }
          },
        )
        : TextFormField(
          decoration: InputDecoration(
            labelText: 'Search Here',
            prefixIcon: Icon(
              Icons.search,
              color: Colors.grey,
            ),
          ),
          controller: searchController,
          onFieldSubmitted: _handleSubmitted,
        ),
        backgroundColor: kBackgroundColor,
          actions: <Widget>[
            IconButton(
                icon: Icon(
                  Icons.search,
                  color: kSecondaryColor,
                ),
                onPressed: () {
                  setState(() {
                    _isSearching = !_isSearching;
                  });
                }),
          ],
      ),
      body: _pages[_selectedPageIndex],
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showFilterModal(context),
        child: Icon(Icons.filter_list),
        backgroundColor: kDarkButtonBg,
      ),
      bottomNavigationBar: BottomNavigationBar(
        onTap: _selectPage,
        items: [
          BottomNavigationBarItem(
            backgroundColor: kBackgroundColor,
            icon: Icon(Icons.school),
            title: Text('Course'),
          ),
          BottomNavigationBarItem(
            backgroundColor: kBackgroundColor,
            icon: Icon(Icons.shopping_basket),
            title: Text('My Course'),
          ),
          BottomNavigationBarItem(
            backgroundColor: kBackgroundColor,
            icon: Icon(Icons.favorite_border),
            title: Text('Wishlist'),
          ),
          BottomNavigationBarItem(
            backgroundColor: kBackgroundColor,
            icon: Icon(Icons.account_circle),
            title: Text('Account'),
          ),
        ],
        backgroundColor: kBackgroundColor,
        unselectedItemColor: kSecondaryColor,
        selectedItemColor: kSelectItemColor,
        currentIndex: _selectedPageIndex,
        type: BottomNavigationBarType.fixed,
      ),
    );
  }

}

Solution

  • There are some ways you can do to reduce the load time of the logo in your AppBar. Since this AppBar is common between the tabs, you should only load it once and avoid loading again every time the tab is changed.

    First is to use StreamBuilder instead of FutureBuilder to reduce the number of loads.

    // Create a StreamController
    final _controller = StreamController<AppLogo>();
    
    // Run fetchMyLogo() in your initState() like in your code
    
    // In your fetchMyLogo(), add the result to the stream
    fetchMyLogo() async {
    
        // ... other lines
    
        if (response.statusCode == 200) {
          // If the server did return a 200 OK response,
          // then parse the JSON.
          var logo = AppLogo.fromJson(jsonDecode(response.body));
          _controller.add(logo);
        }
    
    // Then, listen to this logo in your StreamBuilder
     @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
    
            // ... other lines
    
            title: !_isSearching
                ? StreamBuilder<AppLogo>(
              stream: _controller.stream,
              builder: (context, snapshot) {
    
              // ... other lines
    
    

    Second is to use the cached_network_image instead of Image.network, so that your logo image is cached, which reduce load time for network images.

    return CachedNetworkImage(
              imageUrl: snapshot.data.darkLogo,
              fit: BoxFit.contain,
              height: 27,
           );
    

    Small note: For each of the page in your _pages list, if you want the page to persist (not reload after every tab change), you can use the AutomaticKeepAliveClientMixin to persist the state of each page, or use IndexedStack like:

    // The IndexedStack will load all pages initially
    body: IndexedStack(
            children: _pages,
            index: _selectedPageIndex,
          ),
    // ... other lines