Implementing ChangeNotifier to StateNotifier

531 Views Asked by At

I've rewriten my ChangeNotifier codes to StateNotifier but the problem is that it does not rebuild when you pressed like or dislike the student button in the student row. I expect that ref.watch() listens to state of likedStudentsNotifier and in case of changes makes StudentRow widget build rebuild. But it does not. Why does it happen? To solution in StudentRow class I can use ConsumerStatefulWidget with setState instead of ConsumerWidget but I wonder how it works?

ChangeNotifier:

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

void main() {
  runApp(const ProviderScope(child: Schoolapp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'School App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(title: 'School App Main Page'),
    );
  }
}

class HomePage extends ConsumerWidget {
  const HomePage({Key? key, required this.title}) : super(key: key);

  final String title;


  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextButton(
              child: Text('${ref.watch(studentsProvider).studentsList.length} Students'),
              onPressed: () {
                _runStudentsPage(context);
              },
            ),
          ],
        ),
      ),
    );
  }

  void _runStudentsPage(BuildContext context) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) {
        return const StudentsPage();
      },
    ));
  }
}

class StudentsPage extends ConsumerWidget {
  const StudentsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Students')),
      body: Column(
        children: [
          PhysicalModel(
            color: Colors.white,
            elevation: 10,
            child: Center(
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 32.0, horizontal: 32.0),
                child: Text(
                    '${ref.watch(studentsProvider).studentsList.length} Students'
                ),
              ),
            ),
          ),
          Expanded(
            child: ListView.separated(
              itemBuilder: (context, index) => StudentRow(
                ref.watch(studentsProvider).studentsList[index],
              ),
              separatorBuilder: (context, index) => const Divider(),
              itemCount: ref.watch(studentsProvider).studentsList.length,
            ),
          ),
        ],
      ),
    );
  }

}

class StudentRow extends ConsumerWidget {
  final StudentModel student;
  const StudentRow(this.student, {
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    bool isLiked = ref.watch(studentsProvider).isLiked(student);
    return ListTile(
      title: Text(student.name + ' ' + student.surName),
      leading: IntrinsicWidth(child: Center(child: Text(student.gender == 'Female' ? '‍' : '‍'))),
      trailing: IconButton(
        onPressed: () {
          ref.read(studentsProvider).like(student, !isLiked);
        },
        icon: Icon(isLiked ?  Icons.favorite : Icons.favorite_border),
      ),
    );
  }
}

class StudentsRepository extends ChangeNotifier {

  final studentsList = [
    StudentModel('Aziz', 'Sancar', 18, 'Male'),
    StudentModel('Iggy', 'Azalea', 20, 'Female'),
    StudentModel('Madonna', 'Rrww', 22, 'Female'),
  ];

  final Set<StudentModel> likedStudents = {};

  void like(StudentModel student, bool isLiked) {
    if (isLiked == true) {
      likedStudents.add(student);
    } else {
      likedStudents.remove(student);
    }
    notifyListeners();
  }

  bool isLiked(StudentModel student) {
    return likedStudents.contains(student);
  }
}

final studentsProvider = ChangeNotifierProvider((ref) {
  return StudentsRepository();
});


class StudentModel {
  String name;
  String surName;
  int age;
  String gender;

  StudentModel(this.name, this.surName, this.age, this.gender);
}

StateNotifier:

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

void main() {
  runApp(const ProviderScope(child: const Schoolapp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'School App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(title: 'School App Main Page'),
    );
  }
}

class HomePage extends ConsumerWidget {
  const HomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextButton(
              child: Text(
                  '${ref.watch(studentsProvider.notifier).state.length} Students'),
              onPressed: () {
                _runStudentsPage(context);
              },
            ),
          ],
        ),
      ),
    );
  }

  void _runStudentsPage(BuildContext context) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) {
        return StudentsPage();
      },
    ));
  }
}

class StudentsPage extends ConsumerWidget {
  const StudentsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Students Page')),
      body: Column(
        children: [
          PhysicalModel(
            color: Colors.white,
            elevation: 10,
            child: Center(
              child: Padding(
                padding: const EdgeInsets.symmetric(
                    vertical: 32.0, horizontal: 32.0),
                child: Text(
                    '${ref.watch(studentsProvider.notifier).state.length} Students'),
              ),
            ),
          ),
          Expanded(
            child: ListView.separated(
              itemBuilder: (context, index) => StudentRow(
                ref.watch(studentsProvider.notifier).state[index],
              ),
              separatorBuilder: (context, index) => const Divider(),
              itemCount: ref.watch(studentsProvider.notifier).state.length,
            ),
          ),
        ],
      ),
    );
  }
}

class StudentRow extends ConsumerWidget {
  final StudentModel student;

  const StudentRow(
    this.student, {
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    bool isStudentLiked =
        ref.watch(likedStudentsProvider.notifier).isLiked(student);

    return ListTile(
      title: Text(student.name + ' ' + student.surName),
      leading: IntrinsicWidth(
          child: Center(
              child: Text(student.gender == 'Female' ? '‍' : '‍'))),
      trailing: IconButton(
        onPressed: () {
          ref
              .read(likedStudentsProvider.notifier)
              .likeStudent(student, !isStudentLiked);
        },
        icon: Icon(isStudentLiked ? Icons.favorite : Icons.favorite_border),
      ),
    );
  }
}

class StudentsRepository extends StateNotifier<List<StudentModel>> {
  StudentsRepository() : super([]);

  addStudent(StudentModel studentToAdd) {
    return state = [...state, studentToAdd];
  }

  @override
  final state = [
    StudentModel('Aziz', 'Sancar', 18, 'Male'),
    StudentModel('Iggy', 'Azalea', 20, 'Female'),
    StudentModel('Madonna', 'Rrww', 22, 'Female'),
  ];
}

class likedStudentsNotifier extends StateNotifier<Set<StudentModel>> {
  likedStudentsNotifier() : super({});

  Set<StudentModel> state = {};

  void likeStudent(StudentModel studentToAdd, bool isLiked) {
    if (isLiked == true) {
      state = {...state, studentToAdd};
    } else {
      state = {
        for (final student in state)
          if (student != studentToAdd) student,
      };
    }
  }

  isLiked(StudentModel student) {
    return state.contains(student);
  }
}

class StudentModel {
  String name;
  String surName;
  int age;
  String gender;

  StudentModel(this.name, this.surName, this.age, this.gender);
}

final likedStudentsProvider =
    StateNotifierProvider<likedStudentsNotifier, Set<StudentModel>>((ref) {
  return likedStudentsNotifier();
});

final studentsProvider = StateNotifierProvider<StudentsRepository, List>((ref) {
  return StudentsRepository();
});

1

There are 1 best solutions below

0
On BEST ANSWER

Your problem was reading the notifier in a bad way ref.watch(provider.notifier).state vs ref.watch(provider), left is bad. I have rewrite your code to cleaner and with better practice in how to handle state in the StateNotifier. You can compare my code with yours and see what's wrong.

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

void main() {
  runApp(const ProviderScope(child: Schoolapp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'School App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(title: 'School App Main Page'),
    );
  }
}

class HomePage extends ConsumerWidget {
  const HomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextButton(
              child: Text(
                '${ref.watch(studentsProvider).length} Students',
              ),
              onPressed: () {
                _runStudentsPage(context);
              },
            ),
          ],
        ),
      ),
    );
  }

  void _runStudentsPage(BuildContext context) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) {
        return const StudentsPage();
      },
    ));
  }
}

class StudentsPage extends ConsumerWidget {
  const StudentsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Students Page')),
      body: Column(
        children: [
          PhysicalModel(
            color: Colors.white,
            elevation: 10,
            child: Center(
              child: Padding(
                padding: const EdgeInsets.symmetric(
                    vertical: 32.0, horizontal: 32.0),
                child: Text('${ref.watch(studentsProvider).length} Students'),
              ),
            ),
          ),
          Expanded(
            child: ListView.separated(
              itemBuilder: (context, index) => StudentRow(
                ref.watch(studentsProvider)[index],
              ),
              separatorBuilder: (context, index) => const Divider(),
              itemCount: ref.read(studentsProvider).length,
            ),
          ),
        ],
      ),
    );
  }
}

class StudentRow extends ConsumerWidget {
  final StudentModel student;

  const StudentRow(
    this.student, {
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ListTile(
      title: Text(student.name + ' ' + student.surName),
      leading: IntrinsicWidth(
        child: Center(
          child: Text(student.gender == 'Female' ? '‍' : '‍'),
        ),
      ),
      trailing: IconButton(
        onPressed: () {
          ref.read(studentsProvider.notifier).like(student);
        },
        icon: Icon(student.isLiked ? Icons.favorite : Icons.favorite_border),
      ),
    );
  }
}

class StudentsRepository extends StateNotifier<List<StudentModel>> {
  StudentsRepository()
      : super([
          StudentModel('Aziz', 'Sancar', 18, 'Male', false),
          StudentModel('Iggy', 'Azalea', 20, 'Female', false),
          StudentModel('Madonna', 'Rrww', 22, 'Female', false),
        ]);

  void add(StudentModel student) {
    state = [...state, student];
  }

  void like(StudentModel student) {
    state = [
      for (var element in state)
        if (element == student)
          StudentModel(
            element.name,
            element.surName,
            element.age,
            element.gender,
            element.isLiked = !element.isLiked,
          )
        else
          element
    ];
  }

  void remove(StudentModel student) {
    state = state.where((e) => e != student).toList();
  }
}

class StudentModel {
  String name;
  String surName;
  int age;
  String gender;
  bool isLiked;

  StudentModel(this.name, this.surName, this.age, this.gender, this.isLiked);

  @override
  bool operator ==(covariant StudentModel model) {
    return model.name == name &&
        model.surName == surName &&
        model.age == age &&
        model.gender == gender &&
        model.isLiked == isLiked;
  }

  @override
  int get hashCode =>
      name.hashCode ^
      surName.hashCode ^
      age.hashCode ^
      gender.hashCode ^
      isLiked.hashCode;
}

final studentsProvider = StateNotifierProvider<StudentsRepository, List>((ref) {
  return StudentsRepository();
});