I’m discovering Flutter and I have a problem using the Provider package. I would like to understand why I’m having this behavior. I’ve prepared a demo app in order to explain this problem:
In my provider class I use a service to communicate with a back-end that I made myself in Symfony:
class AppProvider extends ChangeNotifier {
String? _token;
List<ToDoModel> _todos = [];
String? get token => _token;
List<ToDoModel> get todos => _todos;
requestToken(String username, String password) async {
_token = await ApiService.getToken(username, password);
notifyListeners();
}
Future<void> fetchToDos() async {
if (_token == null) {
throw Exception('Token is null');
}
try {
_todos = [];
var todos = await ApiService.fetchToDos(_token!);
todos.forEach((todo) {
_todos.add(ToDoModel.fromJson(todo));
});
notifyListeners();
} catch (e) {
throw Exception('Failed to fetch todos, $e');
}
}
}
Here is my HomeScreen:
class HomeScreen extends StatelessWidget {
static const routeName = '/home';
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final appProvider = Provider.of<AppProvider>(context);
final isLogged = appProvider.token != null;
return Builder(builder: (context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: isLogged
? const TodosList()
: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pushNamed(LoginScreen.routeName);
},
child: const Text('Login'),
),
),
);
});
}
}
Once the login is done a notifyListener() is called and HomeScreen() is correctly rebuild, in fact if I replace the TodosList widget with a Placeholder() everything works great. My problem happens when in the TodosList widget I call fetchTodos() in AppProvider, once the request is fulfilled and data is set in the _todos variable notifyListeners() is called again. Now for a fraction of second the ListTodos widgets appears but right after a blank page is shown (not even the appBar is show) and in the console I’m not having any error message.
Here my ListTodos widget implementation:
class TodosList extends StatefulWidget {
const TodosList({Key? key}) : super(key: key);
@override
State<TodosList> createState() => _TodosListState();
}
class _TodosListState extends State<TodosList> {
var _isInit = true;
@override
void didChangeDependencies() {
if (_isInit) {
Provider.of<AppProvider>(context, listen: false).fetchToDos();
}
_isInit = false;
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final appProvider = Provider.of<AppProvider>(context, listen: false);
return ListView.builder(
itemCount: appProvider.todos.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(appProvider.todos[index].title),
subtitle: Text(appProvider.todos[index].body),
);
},
);
}
}
If I call fetchTodos() directly in the requestToken() method and I keep ListTodos stateless everything works.
2
Answers
Have you tried wrapping with Consumer?
Change your HomePage to stateful and in initState() call your fetchTodo() with addPostFrameCallback.
and In body use Consumer: