2

I have TabBarView with six tabs. I'm trying to show all the installed apps in first tab.

Even after apps is filled CircularProgressIndicator is displayed. Apps are listed once the first tab is revisited.

AppScreenC is called for first tab.

final model = AppModel();

class AppScreenC extends StatefulWidget {
  @override
  _AppScreenCState createState() => _AppScreenCState();
}

class _AppScreenCState extends State<AppScreenC> {
  List<Application> apps = model.loadedApps();
      
  @override
  Widget build(BuildContext context) => _buildApps();

  Widget _buildApps() => apps != null
      ? ListView.builder(
          itemCount: apps.length,
          itemBuilder: (BuildContext context, int index) =>
              _buildRow(apps[index]))
      : Center(child: CircularProgressIndicator());

  Widget _buildRow(ApplicationWithIcon app) {
    final saved = model.getApps().contains(app.apkFilePath);
    return ListTile(
      leading: Image.memory(app.icon, height: 40),
      trailing: saved
          ? Icon(Icons.check_circle, color: Colors.deepPurple[400])
          : Icon(Icons.check_circle_outline),
      title: Text(app.appName),
      onTap: () => setState(() => saved
          ? model.removeApp(app.apkFilePath)
          : model.addApp(app.apkFilePath)),
    );
  }
}

AppModel class has all the necessary methods.

  class AppModel{
  final _saved = Set<String>();
  List<Application> apps;

  AppModel() {
    loadApps();
  }

  Set<String> getApps() {
    return _saved;
  }

  addApp(String apkPath) {
    _saved.add(apkPath);
  }

  removeApp(String apkPath) {
    _saved.remove(apkPath);
  }

  loadApps() async {
    apps = await DeviceApps.getInstalledApplications(
        onlyAppsWithLaunchIntent: true,
        includeSystemApps: true,
        includeAppIcons: true);
    apps.sort((a, b) => a.appName.compareTo(b.appName));
  }

  loadedApps() => apps;
}

This is happening because apps is null, when the screen was called first time. It loads the apps in background. Upon visiting the screen again, apps are displayed.

Any help is welcome

shanthosh
  • 57
  • 7
  • Yeah, this is what I wanted. I'll try the same in my case – shanthosh Sep 07 '20 at 05:16
  • I tried the first example. I added `future = loadApps()` in `initState()` and added `FutureBuilder` to `build` of `AppScreenC`. Progress bar is displayed every time I visit first tab. Is it because of calling a `StatefulWidget` in `TabBarView`? My tab bar view looks like `TabBarView( children: [AppScreenC(), //Other tabs])`. – shanthosh Sep 07 '20 at 05:40
  • Second example is working perfectly. – shanthosh Sep 07 '20 at 05:55
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/221074/discussion-between-shanthosh-and-pskink). – shanthosh Sep 07 '20 at 05:55

1 Answers1

2

What you can do is calling setState() after your Function is done. You need to change loadedApp to return a Future:

class AppScreenC extends StatefulWidget {
  @override
  _AppScreenCState createState() => _AppScreenCState();
}

class _AppScreenCState extends State<AppScreenC> {
  @override
 void initState(){
 super.initState();
 model.loadApps().then((loadedApps){ //loadApps() will return apps and you don't need loadedApps() method anymore
 setState((){ //rebuilds the screen
  apps = loadedApps
   })});
}
      
  @override
  Widget build(BuildContext context) => _buildApps();

  Widget _buildApps() => apps != null
      ? ListView.builder(
          itemCount: apps.length,
          itemBuilder: (BuildContext context, int index) =>
              _buildRow(apps[index]))
      : Center(child: CircularProgressIndicator());

  Widget _buildRow(ApplicationWithIcon app) {
    final saved = model.getApps().contains(app.apkFilePath);
    return ListTile(
      leading: Image.memory(app.icon, height: 40),
      trailing: saved
          ? Icon(Icons.check_circle, color: Colors.deepPurple[400])
          : Icon(Icons.check_circle_outline),
      title: Text(app.appName),
      onTap: () => setState(() => saved
          ? model.removeApp(app.apkFilePath)
          : model.addApp(app.apkFilePath)),
    );
  }
}

And your AppModel will look like this:

class AppModel{
  final _saved = Set<String>();
  List<Application> apps;

  AppModel() {
    loadApps();
  }

  Set<String> getApps() {
    return _saved;
  }

  addApp(String apkPath) {
    _saved.add(apkPath);
  }

  removeApp(String apkPath) {
    _saved.remove(apkPath);
  }

  Future loadApps() async {
    apps = await DeviceApps.getInstalledApplications(
        onlyAppsWithLaunchIntent: true,
        includeSystemApps: true,
        includeAppIcons: true);
    apps.sort((a, b) => a.appName.compareTo(b.appName));
    return Future.value(apps);
  }

}

You can also use FutureBuilder as suggested in the comments

Morez
  • 2,085
  • 2
  • 10
  • 33
  • Thank you!!! for the answer. But this code shows `circularProgressIndicator` every time first tab is visited. To overcome that, I added a null check in loadApps(). `Future loadApps() async { if(apps == null) { // same code } return Future.value(apps); }` Is it right? – shanthosh Sep 06 '20 at 12:30
  • You have 2 options. you can define app as a global variable so when you visit this page (if already visited), then `app` is not null anymore and you don't need to run `loadApps()` anymore. You can also visit https://stackoverflow.com/a/51738269/12828249 to learn how to preserve the screen state so that when you come back to this screen, you don't build the screen again and with that, you won't see the `circularProgressIndicator` if `apps` are not null – Morez Sep 06 '20 at 12:48
  • Will there be any performance issue by making app as a global variable – shanthosh Sep 06 '20 at 12:52
  • Not that I know of but you have to be careful not to change the value somewhere else in the app as global variables are accessible throughout the program. Check out https://stackoverflow.com/questions/29182581/global-variables-in-dart for creating global variables – Morez Sep 06 '20 at 12:58
  • 1
    If it's constantly being reloaded or is accessed in too many widgets, then yes. Otherwise, it should be fine. – Scott Godfrey Sep 06 '20 at 12:59
  • Also have a look at https://stackoverflow.com/a/10525602/12828249 for global variables disadvantages – Morez Sep 06 '20 at 12:59