I'm struggling to understand how to initialize a Flutter cubit such that it can load data automatically when it's first created.
I saw 2 general recommendations during research, people say you can either call an initialization method in the cubit constructor or you can call it in the BlocProvider
where the cubit is created using ..
notation.
I put together an example screen (code below) based on research but the cubit does not print the initialize message in the initialize
method automatically as I would expect.
It does however, print the following sequence of log messages only after I click the FAB (just a simple call to a separate test method in the cubit).
[log] TestScreen1Cubit constructor
[log] TestScreen1Cubit.initialize() - Initialized from [TestScreen1Cubit Constructor]
[log] TestScreen1Cubit.initialize() - Initialized from [TestScreen1 BlocProvider]
[log] TestScreen1Cubit.printHello() - Hello
Any ideas or suggestions about why the TestScreen1Cubit
's initialize
method isn't called automatically?
Here is my project structure:
lib/
app.dart
main.dart
test_screen_1/
cubit/
test_screen1_cubit.dart
test_screen1_state.dart
repository/
test_screen_1_repository.dart
data_provider/
test_screen_1_data_provider.dart
ui/
test_screen_1.dart
test_view_1.dart
utils/
MyDebuggingBlocObserver.dart
Here are my test project files:
Note that I have a repository and data_provider here which would be utilized during the cubit initialization but they aren't actually used in this current example because the cubit initialize method isn't called automatically for some reason
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'app.dart';
import 'utils/MyDebuggingBlocObserver.dart';
Future<void> main() async {
BlocOverrides.runZoned(
() => runApp(
App(),
),
blocObserver: MyDebuggingBlocObserver(),
);
}
app.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hello_new_project/test_screen_1/data_provider/test_screen_1_data_provider.dart';
import 'package:hello_new_project/test_screen_1/repository/test_screen_1_repository.dart';
import 'package:hello_new_project/test_screen_1/ui/test_screen_1.dart';
class App extends MaterialApp {
App({Key? key})
: super(
key: key,
routes: {
TestScreen1.routeName: (context) => RepositoryProvider(
create: (context) => TestScreen1Repository(
TestScreen1DataProvider(),
),
child: const TestScreen1(),
),
},
initialRoute: TestScreen1.routeName,
);
}
test_screen_1.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hello_new_project/test_screen_1/cubit/test_screen1_cubit.dart';
import 'package:hello_new_project/test_screen_1/repository/test_screen_1_repository.dart';
import 'test_view_1.dart';
class TestScreen1 extends StatelessWidget {
static const String routeName = "/TestScreen1";
const TestScreen1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TestScreen1Cubit(
context.read<TestScreen1Repository>()
)..initialize("TestScreen1 BlocProvider"), //********** Suggestion #2 ********** //
child: const TestView1(),
);
}
}
test_view_1.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;
import 'package:hello_new_project/test_screen_1/cubit/test_screen1_cubit.dart';
class TestView1 extends StatelessWidget {
const TestView1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("TestView1"),
),
body: _buildBodyContent(context),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
context.read<TestScreen1Cubit>().printHello();
},
),
);
}
//region Build Methods
Widget _buildBodyContent(BuildContext context) {
return const SafeArea(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Hello"),
),
);
}
//endregion Build Methods
}
test_screen1_cubit.dart
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../repository/test_screen_1_repository.dart';
import 'dart:developer' as developer;
part 'test_screen1_state.dart';
class TestScreen1Cubit extends Cubit<TestScreen1State> {
//region Class vars
final TestScreen1Repository _testScreen1Repository;
//endregion Class vars
//region Constructor
TestScreen1Cubit(this._testScreen1Repository) : super(TestScreen1Initial()) {
developer.log("TestScreen1Cubit constructor");
initialize("TestScreen1Cubit Constructor"); // ********** Suggestion #1 ********** //
}
//endregion Constructor
//region Public methods
/// Prints a simple log message with param [fromWhere] indicating where this
/// method was called from
void initialize(String fromWhere) {
developer
.log("TestScreen1Cubit.initialize() - Initialized from [$fromWhere]");
}
/// Simple public method to print hello, called by the FAB for testing
void printHello() {
developer.log("TestScreen1Cubit.printHello() - Hello");
}
//endregion Public methods
//region Private methods
//endregion Private methods
}
test_screen1_state.dart
part of 'test_screen1_cubit.dart';
@immutable
abstract class TestScreen1State extends Equatable {
const TestScreen1State();
}
class TestScreen1Initial extends TestScreen1State {
@override
List<Object> get props => [];
}
class TestScreen1Loading extends TestScreen1State {
@override
List<Object> get props => [];
}
class TestScreen1Loaded extends TestScreen1State {
@override
List<Object> get props => [];
}
pubspec.yaml
name: hello_new_project
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=2.19.2 <3.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_bloc: ^8.1.2
equatable: ^2.0.5
fluttertoast: ^8.2.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
MyDebuggingBlocObserver.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;
class MyDebuggingBlocObserver extends BlocObserver {
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
developer.log('${bloc.runtimeType} $change');
}
}
I have tried to implement both of the suggestions I saw (see the lines with ********** Suggestion <1 or 2> **********
) but the initialize method still does not get called automatically. It looks like it only gets called after the call to the FAB test method is made instead of automatically as I would expect. The log from the FAB test method still appears after the constructor and initialize logs as expected but does the cubit not get created until a method call to it is made?
this is normal behavior. the reason it doesn't match your expectations is that BlocProvider creating TestScreen1Cubit in a lazy way - just when you start to use it and not before. that's why you see all the logs only after pressing FAB.
in order for TestScreen1Cubit to be created immediately, you can use BlocBuilder in your code where you need it.
for example, in TestView1:
Widget _buildBodyContent(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: BlocBuilder<TestScreen1Cubit, TestScreen1State>(
builder: (context, state) {
// you can use your state here
return const Text("Hello");
}
),
),
);
}