Search code examples
flutterdartflutter-providerdart-null-safety

Nullable class instance with Provider


I have a class named User which basically has two variables. name and age. Also, I have another class named UserList.The aim of this class is to add the user objects to a list and return that list.

User model

class User with ChangeNotifier{
    late String? name;
    late int? age;

  //set user name;
  setName(String name){
    this.name=name;
  }

  //set user age
  setAge(int age){
    this.age=age;
  }

}

UserList class


class UserList with ChangeNotifier{
  final List<User> _list=[];

  //add a new user to the list.
  void addUserToList(User user){
  _list.add(user);
  notifyListeners();
  }
  //return the private list of users.
List<User>getUsers() =>_list;


}

Here is the scenario. I want to insert a value from one page and view that inserted value on the second page. Look at the pictures below. On the first page,

  1. I insert the age and name values
  2. Assign those values in a User object instance (user.setName, user.setAge)
  3. Add that user object instance in a UserList list
  4. Use provider to provide that list.(Provider.of<UserList>(context).addUserToList(user);

First Page

first pic

First Page Code


void main() {
  runApp(

      MultiProvider(
          providers: [
            ChangeNotifierProvider(create: (context) => UserList()),
            ChangeNotifierProvider(create: (context) => User()),
          ],
          child: const MyApp())
  
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}
class MyHomePage extends StatelessWidget {
  TextEditingController nameController = TextEditingController();
  TextEditingController ageController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    User user =User();
    return Scaffold(
      backgroundColor: Colors.grey[300],
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
              TextField(
                controller: nameController,
                decoration: const InputDecoration(
                  hintText: "Name",
                ),
              ),
            TextField(
              controller: ageController,
              decoration: const InputDecoration(
                hintText: "Age",
              ),
            ),
            ElevatedButton(onPressed: (){
             //providing userList
                user.setName(nameController.text); //setting user name
                user.setAge(int.parse(ageController.text)); // setting user age
               Provider.of<UserList>(context,listen: false).addUserToList(user); //providing the list of users
                nameController.text=""; //after insertion, clearing the text field
                ageController.text="";//after insertion, clearing the text field
                user=User(); // instantiating a new user object for the following insertion.
                Navigator.push(context, MaterialPageRoute(builder: (context)=>const ViewUser()));
            }, child: const Text("Submit"))

          ],
        ),
      ),
    );
  }
}


On the second page, display those inserted values in a ListView. Let's head to the main problem. Each item of ListViewis wrapped with a GestureDetector to be able to gain functionality.I hope everything is clear up to now. The problem is that; When I click on each item I want to return to the first page. But, this time the TextField shouldn't be empty. It must be replaced with the value of that item. For exmple. If I click on John Nash and 43. The First page must pop up with field value of that list item. To be able to do that I use another Provider to provide User object. But, I couldn't do it because of null safety it gives an error about the user object is null. Is there any idea that could be helpful.

Second Page

second page

Second Page code

class ViewUser extends StatelessWidget {
  const ViewUser({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    List<User> userList = Provider.of<UserList>(context).getUsers();
    return Scaffold(
      appBar: AppBar(
        title: const Text("Users List"),
      ),
      body: ListView.builder(
          padding: const EdgeInsets.all(8),
          itemCount: userList.length,
          itemBuilder: (BuildContext context, int index) {
            return Column(
              children: [
                const SizedBox(height: 15,),
                GestureDetector(
                  onTap: (){
                    Provider.of<User>(context,listen: false)
                        .setName(userList[index].name!);
                    Provider.of<User>(context,listen: false)
                        .setAge(userList[index].age!);
                    Navigator.push(context, MaterialPageRoute(builder: (context)=>MyHomePage()));
                  },
                  child: Container(
                    height: 50,
                    color: Colors.amber,
                    child: Center(
                      child: Row(
                        children: [
                          Text("Name: " + userList[index].name!),
                          const SizedBox(
                            width: 40,
                          ),
                          Text("Age:" + userList[index].age.toString()),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            );
          }),
    );
  }
}


Solution

  • While you are using Navigator.push you can pass user as arguments.

    GestureDetector(
      onTap: () {
        final user = userList[index];
        Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => MyHomePage(),
              settings: RouteSettings(
                arguments: user,
              )),
        );
      },
    

    MyHomePage build method will be

     @override
      Widget build(BuildContext context) {
        User user = User();
        User? args = ModalRoute.of(context)?.settings.arguments as User?;
    
        if (args != null) {
          nameController.text = args.name ?? "";
          ageController.text = args.age.toString();
        }
        return Scaffold(....
    

    You can also use Navigator.pop in this case.

    GestureDetector(
      onTap: () {
        final user = userList[index];
        Navigator.pop(context, user);
      },
    

    And to receive data on MyHomePage

    onPressed: () async {
    //....
    final data = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const ViewUser(),
      ),
    );
    if (data != null) {
      nameController.text = data.name ?? "";
      ageController.text = data.age.toString();
    }
    

    More about navigate-with-arguments.