Why isn't the screen rebuild in flutter bloc? - flutter

I split the screen into 2 parts, the upper part to change the state when each time I clicked, the bottom part is a list, when the state is loaded, I try to add an event to sort, it still works but the screen don't rebuild again
My code here
Hope everyone can help! Thank
widget
Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: BlocProvider(
create: (_) => referrerBloc,
child: Column(
children: [
flag1(referrerBloc.add(ReferrerSortEvent)),
flag2(referrerBloc.add(ReferrerSortEvent)),
flag3(referrerBloc.add(ReferrerSortEvent)),
BlocBuilder<ReferrerBloc, ReferrerState>(
builder: (__, state) {
if (state is ReferrerLoadingState) {
referrerBloc.add(ReferrerLoadedEvent());
return const Center(
child: CircularProgressIndicator(),
);
} else if (state is ReferrerLoadedState) {
final listReferrer = state.listReferrer;
return Expanded(
child: ListView.builder(
itemCount: listReferrer.length,
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: itemUser(listReferrer.elementAt(index)),
);
},
),
);
}
return const Center(
child: Text(
'Error!',
style: TextStyle(color: Colors.red),
),
);
},
),
],
),
),
),
)
My code here Hope everyone can help! Thank
My code here Hope everyone can help! Thank
event
part of 'referrer_bloc.dart';
abstract class ReferrerEvent extends Equatable {
const ReferrerEvent();
#override
List<Object> get props => [];
}
class ReferrerLoadingEvent extends ReferrerEvent {}
class ReferrerLoadedEvent extends ReferrerEvent {}
class ReferrerSortEvent extends ReferrerEvent {
const ReferrerSortEvent({required this.flag, required this.time});
final int flag;
final int time;
}
class ReferrerErrorEvent extends ReferrerEvent {}
My code here Hope everyone can help! Thank
My code here Hope everyone can help! Thank
state
part of 'referrer_bloc.dart';
abstract class ReferrerState extends Equatable {
const ReferrerState();
}
class ReferrerLoadingState extends ReferrerState {
const ReferrerLoadingState();
#override
List<Object> get props => [];
}
class ReferrerLoadedState extends ReferrerState {
const ReferrerLoadedState({required this.listReferrer});
final List<ItemReferrer> listReferrer;
#override
List<Object> get props => [listReferrer];
}
My code here Hope everyone can help! Thank
My code here Hope everyone can help! Thank
bloc
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:helloapp/referrer/models/item_referrer.dart';
part 'referrer_event.dart';
part 'referrer_state.dart';
class ReferrerBloc extends Bloc<ReferrerEvent, ReferrerState> {
ReferrerBloc() : super(const ReferrerLoadingState()) {
on<ReferrerEvent>((event, emit) {
});
on<ReferrerLoadedEvent>((event, emit) {
final listUser = [
Item1,
......
];
emit(ReferrerLoadedState(listReferrer: listUser));
});
on<ReferrerSortEvent>((event, emit) {
print(event.flag);
final listUser = [
Item1,
........
];
final List<ItemReferrer> listResult = [];
if (event.flag == 0) {
listUser.forEach(listResult.add);
} else if (event.flag == 1) {
listUser.forEach((element) {
if (element.flag == 1) {
listResult.add(element);
}
});
}
emit(ReferrerLoadedState(listReferrer: listResult));
});
}
}

You can use TabBar instead of Column.
Here's the official link of Flutter: https://docs.flutter.dev/cookbook/design/tabs

Related

Flutter Bloc, UI not updating when state is emmited

I have an app where I'm emitting budgetLoaded() in several events. I have noticed that if I don't emit a different state with my budgetLoading even the app doesn't seem to respond to the change.
import 'package:bloc/bloc.dart';
import 'package:budget_app/model/budget.dart';
import 'package:budget_app/model/budget_repository.dart';
import 'package:equatable/equatable.dart';
part 'budget_event.dart';
part 'budget_state.dart';
class BudgetBloc extends Bloc<BudgetEvent, BudgetState> {
final BudgetRepository budgetRepository;
BudgetBloc({required this.budgetRepository}) : super(BudgetInitial()) {
on<AppStarted>((event, emit) async {
emit(BudgetLoading());
if (await budgetRepository.hasBudget()) {
emit(BudgetLoaded(budget: budgetRepository.budget));
} else {
emit(BudgetLoaded(budget: budgetRepository.budget));
}
});
on<Withdraw>((event, emit) {
budgetRepository.budget.withdraw(event.amount);
emit(BudgetLoaded(budget: budgetRepository.budget));
});
on<Deposit>((event, emit) {
emit(BudgetLoading());
budgetRepository.budget.deposit(event.amount);
emit(new BudgetLoaded(budget: budgetRepository.budget));
});
}
}
This is an issue with the deposit event. This code works.
import 'package:bloc/bloc.dart';
import 'package:budget_app/model/budget.dart';
import 'package:budget_app/model/budget_repository.dart';
import 'package:equatable/equatable.dart';
part 'budget_event.dart';
part 'budget_state.dart';
class BudgetBloc extends Bloc<BudgetEvent, BudgetState> {
final BudgetRepository budgetRepository;
BudgetBloc({required this.budgetRepository}) : super(BudgetInitial()) {
on<AppStarted>((event, emit) async {
emit(BudgetLoading());
if (await budgetRepository.hasBudget()) {
emit(BudgetLoaded(budget: budgetRepository.budget));
} else {
emit(BudgetLoaded(budget: budgetRepository.budget));
}
});
on<Withdraw>((event, emit) {
budgetRepository.budget.withdraw(event.amount);
emit(BudgetLoaded(budget: budgetRepository.budget));
});
on<Deposit>((event, emit) {
budgetRepository.budget.deposit(event.amount);
emit(new BudgetLoaded(budget: budgetRepository.budget));
});
}
}
This code does not work the state that is emitted is correct, I have unit tests to test this and I've done debugging.
This is the main page, the BlocProvider is in the Main file above the Material App so it should be global.
import 'package:budget_app/bloc/budget_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class WalletScreen extends StatefulWidget {
const WalletScreen({Key? key}) : super(key: key);
#override
State<WalletScreen> createState() => _WalletScreenState();
}
class _WalletScreenState extends State<WalletScreen> {
#override
Widget build(BuildContext context) {
var theme = Theme.of(context);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => onPressed(context),
child: Icon(Icons.add),
),
body: BlocBuilder<BudgetBloc, BudgetState>(
bloc: BlocProvider.of<BudgetBloc>(context),
builder: (context, state) {
if (state is BudgetLoaded) {
var budget = state.budget;
return SafeArea(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
child: Center(
child: Column(
children: [
Text(
'Current Balance:',
style: TextStyle(
color: Colors.white, fontSize: 25),
),
Text(
'${budget.numberFormatted}',
style: TextStyle(
color: Colors.green, fontSize: 50),
),
],
),
),
color: Colors.blueGrey,
),
),
),
],
),
),
);
} else {
return CircularProgressIndicator();
}
},
),
);
}
onPressed(BuildContext context) {
var budgetBloc = BlocProvider.of<BudgetBloc>(context);
budgetBloc.add(Deposit(100.00));
}
}
It's not a huge issue I just want to understand why it doesn't work.
EVENT
part of 'budget_bloc.dart';
abstract class BudgetEvent extends Equatable {
const BudgetEvent();
#override
List<Object> get props => [];
}
class AppStarted extends BudgetEvent {}
class Withdraw extends BudgetEvent {
final double amount;
const Withdraw(this.amount);
}
class Deposit extends BudgetEvent {
final double amount;
const Deposit(this.amount);
}
STATE
part of 'budget_bloc.dart';
abstract class BudgetState extends Equatable {
const BudgetState();
#override
List<Object> get props => [];
}
class BudgetInitial extends BudgetState {}
class BudgetLoading extends BudgetState {}
class BudgetLoaded extends BudgetState {
final Budget budget;
BudgetLoaded({required this.budget});
}
You can see in the source of emit that it won't publish a state if it is equal to the last one:
void emit(State state) {
try {
if (isClosed) {
throw StateError('Cannot emit new states after calling close');
}
if (state == _state && _emitted) return;
onChange(Change<State>(currentState: this.state, nextState: state));
_state = state;
_stateController.add(_state);
_emitted = true;
} catch (error, stackTrace) {
onError(error, stackTrace);
rethrow;
}
}
By default, == returns true if two objects are the same instance. This could lead to redundant state change notifications, so we usually override == and hashCode, to explicitly tell what counts as equal.
In your example Equatable overrides == and hashCode for you so you don't have to write boilerplate code. The problem is, your props list is empty, so every instance of the same class will be equal to each other. This is why you don't see a state change after the first one.
You should override the props in BudgetLoaded, so Equatable knows what should be compared:
class BudgetLoaded extends BudgetState {
final Budget budget;
BudgetLoaded({required this.budget});
#override
List<Object> get props => [budget];
}
And don't forget to update the Budget class too so that == and hashCode say they are equal, when you think they should be equal.

Flutter: Bloc not navigating to another screen after succesful state update

I'm trying to navigate to another page using blocs / cubits. I have one cubit that successfully navigates to another page upon completion of a method, but for some reason, it doesn't work on another cubit, despite successful state change, and operation done on the method.
class WalletCreateDialog extends StatefulWidget {
const WalletCreateDialog({required this.mnemonic});
final String mnemonic;
#override
_WalletCreateDialogState createState() => _WalletCreateDialogState();
}
class _WalletCreateDialogState extends State<WalletCreateDialog> {
#override
void initState() {
BlocProvider.of<WalletCreateCubit>(context)
.addCreatedWalletToWalletList(widget.mnemonic);
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocListener<WalletCreateCubit, WalletCreateState>(
listener: (context, state) {
if (state is WalletAdded) {
Navigator.of(context).pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
content: Text(
'Wallet added! Navigating back to home screen...',
),
),
);
Navigator.of(context).pushNamedAndRemoveUntil(
WalletOverviewHomeScreen.routeName,
(route) => false,
);
}
},
child: AlertDialog(
content: Container(
height: MediaQuery.of(context).size.height * 0.08,
child: Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text("Adding wallet..."),
const LoadingIndicator(),
],
),
),
],
),
),
),
);
}
}
In the line of code above, it successfully navigates to WalletOverviewHomeScreen upon successful completion of the addCreatedWalletToWalletList method.
class WalletDeleteDialog extends StatefulWidget {
const WalletDeleteDialog({required this.walletAddress});
final String walletAddress;
#override
State<WalletDeleteDialog> createState() => _WalletDeleteDialogState();
}
class _WalletDeleteDialogState extends State<WalletDeleteDialog> {
#override
void initState() {
BlocProvider.of<WalletDeleteCubit>(context)
.deleteWallet(widget.walletAddress);
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocListener<WalletDeleteCubit, WalletDeleteState>(
listener: (context, state) {
if (state is WalletDeleteFinished) {
Navigator.of(context).pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
content: Text(
'Wallet deleted! Navigating back to home screen...',
),
),
);
Navigator.of(context).pushNamedAndRemoveUntil(
WalletOverviewHomeScreen.routeName,
(route) => false,
);
}
},
child: AlertDialog(
content: Container(
height: MediaQuery.of(context).size.height * 0.08,
child: Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text("Deleting wallet..."),
const LoadingIndicator(),
],
),
),
],
),
),
),
);
}
}
On the other hand, in the line of code above, it doesn't navigate to the same screen after completion of the method. I've already verified that the state has changed in both cubits. Additionally, hot restarting the app would actually show that what was supposed to get deleted, did actually get deleted, thus there's no issue with regards to the implementation of the deleteWallet method itself.
How can I navigate to the WalletOverviewHomeScreen after completion of the deleteWallet method?
For context, below are the state classes for the Cubits.
part of 'wallet_create_cubit.dart';
abstract class WalletCreateState extends Equatable {
const WalletCreateState();
#override
List<Object> get props => [];
}
class WalletCreateInitial extends WalletCreateState {
const WalletCreateInitial();
#override
List<Object> get props => [];
}
class WalletCreateLoading extends WalletCreateState {
const WalletCreateLoading();
#override
List<Object> get props => [];
}
class WalletCreated extends WalletCreateState {
final String mnemonic;
const WalletCreated({required this.mnemonic});
#override
List<Object> get props => [mnemonic];
}
class WalletAdding extends WalletCreateState {
const WalletAdding();
#override
List<Object> get props => [];
}
class WalletAdded extends WalletCreateState {
const WalletAdded();
#override
List<Object> get props => [];
}
part of 'wallet_delete_cubit.dart';
abstract class WalletDeleteState extends Equatable {
const WalletDeleteState();
#override
List<Object> get props => [];
}
class WalletDeleteInitial extends WalletDeleteState {
const WalletDeleteInitial();
#override
List<Object> get props => [];
}
class WalletDeleteOngoing extends WalletDeleteState {
const WalletDeleteOngoing();
#override
List<Object> get props => [];
}
class WalletDeleteFinished extends WalletDeleteState {
const WalletDeleteFinished();
#override
List<Object> get props => [];
}
remove Navigator.of(context).pop();
because you don't need it. when you use Navigator.of(context).pushNamedAndRemoveUntil
Looks like the fix was to directly copy the contents of deleteFromWallet to the deleteWallet function. That is, in the WalletDeleteCubit it went from this:
Future<void> deleteWallet(String address) async {
FlutterSecureStorage storage = FlutterSecureStorage();
emit(WalletDeleteOngoing());
deleteFromWallet(storage, address);
debugPrint("Wallet with address: $address is deleted");
emit(WalletDeleteFinished());
debugPrint('Emit WalletDeleteFinished');
}
To this:
void deleteWallet(String address) async {
FlutterSecureStorage storage = FlutterSecureStorage();
emit(WalletDeleteOngoing());
await storage.delete(
key: WalletOverviewHomeScreen.walletKey + address,
);
debugPrint("Wallet with address: $address is deleted");
emit(WalletDeleteFinished());
debugPrint('Emit WalletDeleteFinished');
}

Problem with events in BLoC Flutter. I always call two events instead of one

I return the state with the TodoLoadedState list in each block method. But when I call the block event in onPressed, the list itself is not returned and I have to add a second call to the block method todoBloc.add(LoadTodos()); But that's not correct. Ideas need to trigger 1 event but to perform 2 actions, the second action is to update the list. Thanks in advance!
todo_bloc
class TodoBloc extends Bloc<TodoEvent, TodoState> {
final TodoRepository todoRepository;
TodoBloc(this.todoRepository) : super(TodoEmptyState()) {
on<LoadTodos>((event, emit) async {
emit(TodoLoadingState());
try {
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
} catch (_) {
emit(TodoErrorState());
}
});
on<CreateTodos>((event, emit) async {
// Todo todo = Todo(description: event.task, isDone: false);
await todoRepository.insertTodo(event.todo);
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
});
on<DeleteTodos>((event, emit) async {
await todoRepository.deleteTodo(event.id);
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
});
on<UpdateTodos>((event, emit) async {
await todoRepository.updateTodo(event.todo);
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
});
}
}
todo_list
class TodoList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final TodoBloc todoBloc = context.read<TodoBloc>();
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
if (state is TodoEmptyState) {
return const Center(
child: Text(
'No Todo',
style: TextStyle(fontSize: 20.0),
),
);
}
if (state is TodoLoadingState) {
return const Center(child: CircularProgressIndicator());
}
if (state is TodoLoadedState) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: state.loadedUser.length,
itemBuilder: (context, index) => ListTile(
title: Column(children: [
Text('${state.loadedUser[index].description}'),
Text('${state.loadedUser[index].id}'),
]),
trailing: IconButton(
onPressed: () {
todoBloc.add(DeleteTodos(id: state.loadedUser[index].id));
todoBloc.add(LoadTodos());
},
home_page
class HomePage extends StatelessWidget {
final todoRepository = TodoRepository();
#override
Widget build(BuildContext context) {
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Todos'),
),
body: SingleChildScrollView(
child: Column(
children: [
TodoList(),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add, size: 32, color: Colors.white),
onPressed: () {
final TodoBloc todoBloc = context.read<TodoBloc>();
final _todoDescriptionFromController = TextEditingController();
showModalBottomSheet(
context: context,
builder: (builder) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
color: Colors.transparent,
child: Container(
todo_state
abstract class TodoState extends Equatable {
const TodoState();
#override
List<Object> get props => [];
}
class TodoLoadingState extends TodoState {}
class TodoEmptyState extends TodoState {}
class TodoLoadedState extends TodoState {
List<dynamic> loadedUser;
TodoLoadedState({required this.loadedUser});
}
class TodoErrorState extends TodoState {}
todo_event
abstract class TodoEvent extends Equatable {
const TodoEvent();
#override
List<Object> get props => [];
}
class LoadTodos extends TodoEvent {}
class CreateTodos extends TodoEvent {
final Todo todo;
const CreateTodos(this.todo);
}
class UpdateTodos extends TodoEvent {
final Todo todo;
const UpdateTodos(this.todo);
}
class DeleteTodos extends TodoEvent {
final int id;
const DeleteTodos({required this.id});
}
class QueryTodo extends TodoEvent {}
event onPressed, everywhere you have to use 2 events to load the updated list
todoBloc.add(UpdateTodos(updateTodo));
todoBloc.add(LoadTodos());
This is the culprit:
abstract class TodoState extends Equatable {
const TodoState();
#override
List<Object> get props => [];
}
You are extending Equatable in TodoState and passing an empty list to props. When other states such as TodoLoadedState extend TodoState they inherit Equatable as well and the empty props.
If you're using Equatable make sure to pass all properties to the
props getter.
This is from bloc faq. Right now all instances of your TodoLoadedState are considered equal. Doesn't matter if you have a TodoLoadedState with hundreds of loadedUser or a TodoLoadedState with none. They are both considered equal and only the first time you pass a new TodoLoadedState the BlocBuilder will update. The consequent ones have no effect since BlocBuilder thinks it is the same as previous one. The reason your LoadTodos event causes a rebuild is that first you emit TodoLoadingState() and then in case of success TodoLoadedState(loadedUser: _loadedTodoList). This alternating between two different states makes it work.
So either don't use Equatable or make sure to pass all the properties to props.
class TodoLoadedState extends TodoState {
final List<dynamic> loadedUser;
TodoLoadedState({required this.loadedUser});
#override
List<Object?> get props => [loadedUser];
}

Implement setstat and bind variables

I'm trying to get and display 2 variable values from another dart file, ("int myId" and "String myMenu") , these variables are updated with every "onTap" widget, my code works, but only if i do a "hot reload", i think that i need to put a "setstate" somewhere, but i'm having difficulty to implement it.
I think the problem is there, my widget text returns "null" to me, but if I hit the menu button and do a "hot reload", it's ok.
displayText.dart
import 'package:flutter/material.dart';
import './menu.dart';
class display extends StatefulWidget {
int myId;
String myMenu;
display(this.myId, this.myMenu);
#override
_displayState createState() => _displayState();
}
class _displayState extends State<display> {
Future myVarUsed() async {
//Each press on the button return the value
setState(() {
print('myIdDsiplay: ${widget.myId}'); // null
print('myMenuDisplay : ${widget.myMenu}'); // null
});
}
#override
void initState() {
super.initState();
myVarUsed();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
height: 250,
width: 250,
child: Row(
children: [
Text('My ID is : ${widget.myId}'),
Text('My menu is : ${widget.myMenu}'),
],
),
);
}
}
This file contains the menu inside a scrollbar, each button return the ID and the name (of the button) and store it in 2 variable ("int myId" and "String myMenu") that i want to pass.
menu.dart
import 'package:flutter/material.dart';
import './mylist.dart';
import './displayText.dart';
class Menu extends StatefulWidget {
static int myId;
static String myMenu;
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Container scrollList() {
final PageController controller = PageController(initialPage: 1, keepPage: true, viewportFraction: 0.35);
return Container(
color: Colors.red,
height: 90,
child: PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: listdata.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
child: gestureDetector_Ontap(index),
);
},
),
);
}
GestureDetector gestureDetector_Ontap(int index) {
return GestureDetector(
onTap: () {
Menu.myId = listdata[index].id;
Menu.myMenu = listdata[index].menuObj;
display(Menu.myId, Menu.myMenu);
print('myIDMenu ${Menu.myId}');
print('myMenuMenu ${Menu.myMenu}');
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'${listdata[index].menuObj}',
),
),
);
}
Widget build(BuildContext context) {
return Container(
child: scrollList(),
);
}
}
This file contains my list and his class
mylist.dart
class listModel {
int id;
String menuObj;
listModel(this.id, this.menuObj);
}
List listdata = [
listModel(0, 'Menu01'),
listModel(1, 'Menu02'),
listModel(2, 'Menu03'),
listModel(3, 'Menu04'),
listModel(4, 'Menu05')
];
And the container
main.dart
import 'package:flutter/material.dart';
import './menu.dart';
import './displayText.dart';
import './mylist.dart';
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
Menu(),
display(Menu.myId, Menu.myMenu),
],
),
),
),
);
}
}
The problem
You're defining Menu this way:
class Menu extends StatefulWidget {
static int myId;
static String myMenu;
#override
_MenuState createState() => _MenuState();
}
When your app starts, myId and myMenu are uninitialized variables, therefore they're implicitely set to null.
Inside _MyHomePageState, you call
display(Menu.myId, Menu.myMenu)
Since you haven't initialized Menu.myId and Menu.myMenu yet, they're still null.
When you tap the GestureDetector, you initialize Menu.myId and Menu.myMenu this way:
Menu.myId = listdata[index].id;
Menu.myMenu = listdata[index].menuObj;
display(Menu.myId, Menu.myMenu);
print('myIDMenu ${Menu.myId}');
print('myMenuMenu ${Menu.myMenu}');
Now, Menu.myId and Menu.myMenu are defined to non-null values. However, this will not update the Container's display(Menu.myId, Menu.myMenu), so they'll still be null, you need to update it by yourself.
The solution
I've added comments through the code, pointing a better approach:
import 'package:flutter/material.dart';
// Avoid displaying the warning "Name types using UpperCamelCase."
class Display extends StatefulWidget {
// Make these fields final and the constructor const
final int myId;
final String myMenu;
const Display(this.myId, this.myMenu);
#override
_DisplayState createState() => _DisplayState();
}
// Avoid displaying the warning "Name types using UpperCamelCase."
class _DisplayState extends State<Display> {
// You don't need this Future nor this initState
//
// Future myVarUsed() async {
// setState(() {
// print('myIdDsiplay: ${widget.myId}'); // null
// print('myMenuDisplay : ${widget.myMenu}'); // null
// });
// }
//
// #override
// void initState() {
// super.initState();
// myVarUsed();
// }
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
height: 250,
width: 250,
child: Row(
children: [
Text('My ID is : ${widget.myId}'),
Text('My menu is : ${widget.myMenu}'),
],
),
);
}
}
class Menu extends StatefulWidget {
// Avoid using mutable static fields
// static int myId;
// static String myMenu;
// To simplify, you can add a onChanged callback to
// be triggered whenever you change `myId` and `myMenu`
final void Function(int myId, String myMenu) onChanged;
const Menu({this.onChanged});
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Container scrollList() {
final PageController controller = PageController(initialPage: 1, keepPage: true, viewportFraction: 0.35);
return Container(
color: Colors.red,
height: 90,
child: PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: listdata.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
child: gestureDetectorOntap(index),
);
},
),
);
}
// Avoid displaying the warning "Name non-constant identifiers using lowerCamelCase."
GestureDetector gestureDetectorOntap(int index) {
return GestureDetector(
onTap: () {
// Make these local variables
int myId = listdata[index].id;
String myMenu = listdata[index].menuObj;
// Call the `onChanged` callback
widget.onChanged(myId, myMenu);
// This widget is being thrown away
// display(Menu.myId, Menu.myMenu);
print('myIDMenu $myId');
print('myMenuMenu $myMenu');
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'${listdata[index].menuObj}',
),
),
);
}
Widget build(BuildContext context) {
return Container(
child: scrollList(),
);
}
}
// Avoid the warning "Name types using UpperCamelCase."
class ListModel {
// You can make these fields final and the constructor const
final int id;
final String menuObj;
const ListModel(this.id, this.menuObj);
}
// You can make this list const to avoid modifying it unintentionally later
const List<ListModel> listdata = [
ListModel(0, 'Menu01'),
ListModel(1, 'Menu02'),
ListModel(2, 'Menu03'),
ListModel(3, 'Menu04'),
ListModel(4, 'Menu05')
];
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// Create fields to store the current `myId` and current `myMenu`
int myId;
String myMenu;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
// Add the `onChanged` callback here, updating this widget state
Menu(
onChanged: (newMyId, newMyMenu) {
setState(() {
myId = newMyId;
myMenu = newMyMenu;
});
}
),
// Access the current values here
Display(myId, myMenu),
],
),
),
),
);
}
}

Why BlocBuilder is stuck in the initial state while using get_it?

I'm using flutter_bloc to manage the states of my app, and get_it to inject the needed dependencies following the idea suggested by the Reso Coder's Flutter Clean Architecture Proposal.
Everything is working fine except that the bloc is not changing its state (it's stuck in the initial state)
Here is the code of the involved classes:
The States
abstract class PaintingsState extends Equatable {
final properties = const <dynamic>[];
PaintingsState([properties]);
#override
List<Object> get props => [properties];
}
class PaintingsLoading extends PaintingsState {}
class PaintingsLoaded extends PaintingsState {
final PaintingCardItems cardItems;
PaintingsLoaded({#required this.cardItems}) : super([cardItems]);
}
class Error extends PaintingsState {
final String message;
Error({#required this.message}) : super([message]);
}
The Events
abstract class PaintingsEvent extends Equatable {
const PaintingsEvent();
#override
List<Object> get props => [];
}
/// Tells the bloc that it needs to load the paintings from the PaintingsRepository
class GetPaintings extends PaintingsEvent {}
The Bloc
const String FILE_NOT_FOUND_MESSAGE = 'FileNotFound Failure';
class PaintingsBloc extends Bloc<PaintingsEvent, PaintingsState> {
final GetPaintingCardItems getCardItems;
PaintingsBloc({#required this.getCardItems}) : super(PaintingsLoading());
#override
Stream<PaintingsState> mapEventToState(PaintingsEvent event) async* {
if (event is GetPaintings) {
yield* _mapGetPaintingsToState();
}
}
Stream<PaintingsState> _mapGetPaintingsToState() async* {
yield PaintingsLoading();
final failureOrPaintingCardItems = await getCardItems(NoParams());
yield failureOrPaintingCardItems.fold(
(failure) => Error(message: _mapFailureToMessage(failure)),
(paintingCardItems) => PaintingsLoaded(cardItems: paintingCardItems));
}
String _mapFailureToMessage(Failure failure) {
switch (failure.runtimeType) {
case FileNotFound:
return FILE_NOT_FOUND_MESSAGE;
default:
return 'Unexpected error';
}
}
}
Dependencies injection
/// Ambient variable to access the service locator
final sl = GetIt.instance;
/// Set up all the objects you want to access later through the service locator [sl]
void setUpServiceLocator() {
initFeatures();
}
void initFeatures() {
//! Features - Paintings
// Bloc
sl.registerLazySingleton<PaintingsBloc>(() => PaintingsBloc(getCardItems: sl<GetPaintingCardItems>()));
// Use cases
sl.registerLazySingleton<GetPaintingCardItems>(() => GetPaintingCardItems(sl<PaintingsRepository>()));
// Repository
sl.registerLazySingleton<PaintingsRepository>(
() => PaintingsRepositoryImpl(dataSource: sl<PaintingsDataSource>()));
// Data sources
sl.registerLazySingleton<PaintingsDataSource>(() => PaintingsDataSourceImpl());
}
main.dart
void main() {
// dependencies injection
setUpServiceLocator();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<PaintingsBloc>(
create: (_) => sl<PaintingsBloc>(),
child: MaterialApp(
title: 'My Paintings',
theme: appTheme,
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
),
);
}
}
Page where I use BlocBuilder
class PaintingsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
...
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Stack(
children: <Widget>[
SafeArea(
child: Column(
...
BlocBuilder<PaintingsBloc, PaintingsState>(
builder: (context, state) {
if(state is PaintingsLoading) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
} else if(state is PaintingsLoaded) {
List<PaintingCardItem> _list = state.cardItems.paintingCardItems;
return Expanded(
child: SizedBox(
child: _list.length != 0
? ListCardView(
cardItems: _list)
: Container(
child: Center(child: Text('Empty list'))),
),
);
} else if(state is Error){
return Container(
child: Center(child: Text(state.message)));
} else {
return Container(
child: Center(child: Text('Unknown Error')));
}
}
)
],
))
],
),
),
);
}
}
So, somehow the state of the bloc does not change from PaintingsLoading to either PaintingsLoaded or Error.
If someone can give me some idea to solve this problem, I will really appreciate it.
I solved it, I just needed to add the event to the bloc. So, my solution was to create another state called PaintingsInitialState like so:
The States
...
class PaintingsInitialState extends PaintingsState {}
...
Then in the Bloc, I just changed the constructor of the bloc.
PaintingsBloc({#required this.getCardItems}) : super(PaintingsInitialState());`
Finally, I added the following condition inside the builder parameter of the BlocBuilder.
if (state is PaintingsInitialState) {
_paintingsBloc.add(GetPaintings());
}
I think that the information provided in the offitial site of the bloc library can be useful to understand how to use bloc pattern and libraries properly - particularly Flutter Wheather Tutorial.