Home > Software design >  Flutter bloc not working after hot reload
Flutter bloc not working after hot reload

Time:03-28

My app using bloc/cubit to display a list of Todo items works fine until I hot reload/hot restart the application!

I have two buttons, when i click these, the cubit state is set to having 3 todo items. Additionally I have two periodic timer which sets the cubit state to having only 1 or 0 todo items again. So the number of items is constantly changing from 1 to 0 until, or if i press a button it momentarily becomes 3.

This works fine until hot reload/restart after which the buttons no longer work! The periodic changes do work however. I can only alleviate this problem by creating my ToDoBloc as a field Initializer within my "MyApp" base widget.

main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
import 'package:todo/todo_bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  late TodoBloc _todoBloc; //!!----IF I CREATE THE TODOBLOC HERE EVERYTHING WORKS--!!
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    _todoBloc = TodoBloc(
      apiController: TodoApiController(),
    );
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(_todoBloc),
    );
  }
}

class MyHomePage extends StatelessWidget {
  TodoBloc _todoBloc;
  MyHomePage(this._todoBloc, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: BlocProvider<TodoBloc>.value(
        value: _todoBloc,
        child: BlocBuilder<TodoBloc, TodoBlocState>(
          builder: (context, state) {
            return Builder(
                builder: (context) => Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        TextButton(
                            onPressed: () => context.read<TodoBloc>().LoadAll(),
                            child: Text(
                              'pressme',
                              style: TextStyle(color: Colors.red),
                            )),
                        ListView.builder(
                            shrinkWrap: true,
                            itemCount: state.todos.length,
                            itemBuilder: (context, index) {
                              var todo = state.todos[index];
                              return CheckboxListTile(
                                value: todo.isFinished,
                                onChanged: (newvalue) {},
                              );
                            }),
                      ],
                    ));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await _todoBloc.LoadAll();
        },
      ),
    );
  }
}

todo_api_controller.dart

class TodoApiController {
  List<Todo> GetAll() {
    return [
      Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
      Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
    ];
  }

  void Delete(String id) {}

  void Update(Todo todo) {}

  Todo Create() {
    return Todo(id: "asdfsdf");
  }
}

class Todo {
  final String id;
  String name;
  bool isFinished;
  DateTime? finishedOn;

  Todo({required String id, String name = "", bool isFinished = false, DateTime? finishedOn = null})
      : id = id,
        name = name,
        isFinished = isFinished,
        finishedOn = finishedOn {}
}

todo_bloc.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';

class TodoBloc extends Cubit<TodoBlocState> {
  static int numberOfInstances = 0;
  int myInstance = -1;
  TodoApiController _apiController;

  List<Todo> todos = [];

  TodoBloc({required TodoApiController apiController})
      : _apiController = apiController,
        super(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)])) {
    numberOfInstances  ;
    myInstance = numberOfInstances;
    Timer.periodic(Duration(seconds: 2), (s) => emit(TodoBlocState()));
    Future.delayed(
        Duration(seconds: 1),
        () => Timer.periodic(
            Duration(seconds: 2), (s) => emit(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)]))));
  }

  Future<void> LoadAll() async {
    /* var newTodos = _apiController.GetAll();
    todos.clear();
    todos.addAll(newTodos);
    var newState = MakeState();
    emit(newState);*/
    emit(TodoBlocState(todos: [
      TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
      TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
      TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
    ]));
  }

  TodoBlocState MakeState() {
    return TodoBlocState(
      todos: todos
          .map((e) => TodoItemState(
                id: e.id,
                finishedOn: e.finishedOn,
                isFinished: e.isFinished,
                name: e.name,
              ))
          .toList(),
    );
  }
}

class TodoBlocState {
  final List<TodoItemState> todos = [];
  TodoBlocState({List<TodoItemState>? todos}) {
    this.todos.addAll(todos ?? []);
  }
}

class TodoItemState {
  final String id;
  final String name;
  final bool isFinished;
  final DateTime? finishedOn;

  TodoItemState({required this.id, required this.name, required this.isFinished, required this.finishedOn});
}

I cant figure out why this is, especially with hot restart, as this should reset all application state.

EDIT: the issue appears after a hot reload(not hot restart) but cannot be fixed by hot restart

EDIT2: the issue is fixed by adding a GlobalKey() to the MyHomePage class. Though I cannot understand why. Can someone explain this to me?

CodePudding user response:

This is happening because you're initializing your TodoBloc inside a build function. Hot reload rebuilds your widgets so it triggers a new call to their build functions.

You should convert it into a StatefulWidget and initilize your TodoBloc inside the initState function:

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late TodoBloc _todoBloc; 

  @override
  void initState() {
    super.initState();
    _todoBloc = TodoBloc(
      apiController: TodoApiController(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(_todoBloc),
    );
  }
}

CodePudding user response:

You really don't need to declare and initialize your TodoBloc at the top of the widget tree then pass it all the way down. BlocProvider creates a new instance that is accessible via context.read<TodoBloc>().

Your MyApp could look like this.

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
        create: (context) => TodoBloc(apiController: TodoApiController()), // this is your bloc being created and initialized
        child: MyHomePage(),
      ),
    );
  }
}

And MyHomePage could be simplified. Note the lack of BlocProvider.value and Builder. All you need is a BlocBuilder and the correct instance of TodoBloc is always accessible with context.read<TodoBloc>().

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: BlocBuilder<TodoBloc, TodoBlocState>(
        builder: (context, state) {
          return Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextButton(
                onPressed: () => context.read<TodoBloc>().LoadAll(),
                child: Text(
                  'pressme',
                  style: TextStyle(color: Colors.red),
                ),
              ),
              ListView.builder(
                shrinkWrap: true,
                itemCount: state.todos.length,
                itemBuilder: (context, index) {
                  var todo = state.todos[index];
                  return CheckboxListTile(
                    value: todo.isFinished,
                    onChanged: (newvalue) {},
                  );
                },
              ),
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await context.read<TodoBloc>().LoadAll();
        },
      ),
    );
  }
}
  • Related