Search code examples
user-interfacedartflutterfrontendcustom-widgets

How to inject a Widget into a custom child Widget and use the child Widgets iteration index?


I am building a flutter app. I have built a class with a constructor in it. I made the constructor so I could customize my ListTile because I am using this class for multiple pages and each time, I need to change the text color and sometimes even add an onTap function.

class AppList extends StatefulWidget {

  @override
    AppListState createState() => AppListState();

    AppList({Key key, this.child}) : super(key: key);

    final Widget child;

}

class AppListState extends State<AppList> {

  Widget child;

  List<Map<String, String>> _installedApps;

  @override
    void initState() {
      super.initState();
    }

  getApps() {
    setState(() {
      installedApps = _installedApps;
      getApp();
    });
  }

  @override
    Widget build(BuildContext context) {

      if (installedApps == null)
        getApps();

      return ListView.builder( 
        itemCount: installedApps == null ? 0 : installedApps.length,
        itemBuilder: (context, index) {
          return child; //This is where the ListTile will go. 
        },
      );
    }

}

//Just in case you were confused, I used a plugin for some of the features

After I built this class, I put it in my Example class.

Example class:

class Example extends StatefulWidget {

  @override 
    ExampleState createState() => ExampleState();

}

class ExampleState extends State<Example> {

  @override
    Widget build(BuildContext context) {
      return MaterialApp (
        debugShowCheckedModeBanner: false,
        home: Scaffold (
          body: Container (
            color: Colors.black,
            child: AppList (
              child: ListTile ( 
                title: Text(installedApps[index]["app_name"]) //this is the text
              ),
            )
          )  
        ),
      );
    }

} 

Then after, I added a ListTile and a text inside of it. But as I was writing the text I realized that I could not put the text I wanted. It was because 'index' was not defined in the Example class.

Is there a good way to put this text in my Example class?

Full Code:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'dart:async';
import 'dart:io';

import 'package:flutter_appavailability/flutter_appavailability.dart';

void main() {
  SystemChrome.setEnabledSystemUIOverlays([]);
  runApp(Example());
}

Future<void> getApp() async {

  if (Platform.isAndroid) {

    installedApps = await AppAvailability.getInstalledApps();

    print(await AppAvailability.checkAvailability("com.android.chrome"));

    print(await AppAvailability.isAppEnabled("com.android.chrome"));

  }
  else if (Platform.isIOS) {
    installedApps = iOSApps;

    print(await AppAvailability.checkAvailability("calshow://"));

  }

}

List<Map<String, String>> installedApp;
List<Map<String, String>> installedApps;
List<Map<String, String>> iOSApps = [
  {
    "app_name": "Calendar",
    "package_name": "calshow://"
  },
  {
    "app_name": "Facebook",
    "package_name": "fb://"
  },
  {
    "app_name": "Whatsapp",
    "package_name": "whatsapp://"
  }
];

class Example extends StatefulWidget {

  @override 
    ExampleState createState() => ExampleState();

}

class ExampleState extends State<Example> {

  @override
    Widget build(BuildContext context) {
      return MaterialApp (
        debugShowCheckedModeBanner: false,
        home: Scaffold (
          body: Container (
            color: Colors.black,
            child: AppList ()
          )  
        ),
      );
    }

}

class AppList extends StatefulWidget {

  @override
    AppListState createState() => AppListState();

  AppList({Key key, this.child}) : super(key: key);

  final Widget child;

}

class AppListState extends State<AppList> {

  Widget child;

  List<Map<String, String>> _installedApps;

  @override
    void initState() {
      super.initState();
    }

  getApps() {
    setState(() {
      installedApps = _installedApps;
      getApp();
    });
  }

  @override
    Widget build(BuildContext context) {

      if (installedApps == null)
        getApps();

      return ListView.builder( 
        itemCount: installedApps == null ? 0 : installedApps.length,
        itemBuilder: (context, index) {
          return ListTile (
            title: Text(installedApps[index]["app_name"])
          );
        },
     );
   }

}

Solution

  • Instead of passing a Widget to your custom AppList you could pass a builder function that returns a Widget and takes the parameters as required, e.g. the index and whatever configuration is required. Something like the following:

    Function definition:

    typedef Widget MyListTileBuilder(int index);
    

    then change the following:

    final Widget child;
    

    to

    final MyListTileBuilder childBuilder;
    

    of course you need to implement your builder method in Example Class:

    Widget MyListTileBuilderImplementation (int index) {
      return ListTile ( 
                    title: Text(installedApps[index]["app_name"]) //this is the text
                  ),
    }
    

    when you build AppList inside example class you pass the method

    AppList (
                  childBuilder: MyListTileBuilderImplementation 
    )
    

    and finally inside AppList you call the builder, instead of adding child widget:

        itemBuilder: (context, index) {
          return childBuilder(index); //This is where the ListTile will go. 
        },
    

    Your complete code with those changes looks like this (I had to comment out the references that you didn't provide):

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    import 'dart:async';
    import 'dart:io';
    
    //import 'package:flutter_appavailability/flutter_appavailability.dart';
    
    typedef Widget MyListTileBuilder(int index);
    
    void main() {
      SystemChrome.setEnabledSystemUIOverlays([]);
      runApp(Example());
    }
    
    Future<void> getApp() async {
    
      if (Platform.isAndroid) {
    
        //installedApps = await AppAvailability.getInstalledApps();
    
        //print(await AppAvailability.checkAvailability("com.android.chrome"));
    
        //print(await AppAvailability.isAppEnabled("com.android.chrome"));
    
      }
      else if (Platform.isIOS) {
        installedApps = iOSApps;
    
        //print(await AppAvailability.checkAvailability("calshow://"));
    
      }
    
    }
    
    List<Map<String, String>> installedApp;
    List<Map<String, String>> installedApps=[
      {"app_name":"app1"},
      {"app_name":"app2"},
      {"app_name":"app3"},
    ];
    List<Map<String, String>> iOSApps = [
      {
        "app_name": "Calendar",
        "package_name": "calshow://"
      },
      {
        "app_name": "Facebook",
        "package_name": "fb://"
      },
      {
        "app_name": "Whatsapp",
        "package_name": "whatsapp://"
      }
    ];
    
    class Example extends StatefulWidget {
    
      @override 
        ExampleState createState() => ExampleState();
    
    }
    
    class ExampleState extends State<Example> {
    
      Widget MyListTileBuilderImplementation (int index) {
        return ListTile ( 
                      title: Text(installedApps[index]["app_name"] + "  index:" + index.toString()) //this is the text
                    );
      }
    
      @override
        Widget build(BuildContext context) {
          return MaterialApp (
            debugShowCheckedModeBanner: false,
            home: Scaffold (
              body: Container (
                color: Colors.white,
                child: AppList (childBuilder: this.MyListTileBuilderImplementation)
              )  
            ),
          );
        }
    
    }
    
    class AppList extends StatefulWidget {
    
      @override
        AppListState createState() => AppListState();
    
      AppList({Key key, this.childBuilder}) : super(key: key);
    
      final MyListTileBuilder childBuilder;
    
    }
    
    class AppListState extends State<AppList> {
    
      List<Map<String, String>> _installedApps;
    
      @override
        void initState() {
          super.initState();
        }
    
      getApps() {
        setState(() {
          installedApps = _installedApps;
          getApp();
        });
      }
    
      @override
        Widget build(BuildContext context) {
    
          if (installedApps == null)
            getApps();
    
          return ListView.builder( 
            itemCount: installedApps == null ? 0 : installedApps.length,
            itemBuilder: (context, index) {
              return  widget.childBuilder(index);
            },
         );
       }
    
    }