I created simple app to test bloc 7.2.0 and faced that BlocBuilder doesn't rebuild after first successful rebuild. On every other trigger bloc emits new state, but BlocBuilder ignores it.
Please note, if I remove extends Equatable and its override from both, state and event, then BlocBuilder rebuilds UI every time Button pressed. Flutter version 2.5.1
If Equatable is necessary, why it's not working with it? If Equatable isn't necessary, why it's been used in initial creation via VSCode extension.
My code:
bloc part
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
//bloc
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial()) {
on<MainButtonPressedEvent>(_onMainButtonPressedEvent);
}
void _onMainButtonPressedEvent(
MainButtonPressedEvent event, Emitter<MainState> emit) {
emit(MainCalculatedState(event.inputText));
}
}
//states
abstract class MainState extends Equatable {
const MainState();
#override
List<Object> get props => [];
}
class MainInitial extends MainState {}
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
}
//events
abstract class MainEvent extends Equatable {
const MainEvent();
#override
List<Object> get props => [];
}
class MainButtonPressedEvent extends MainEvent {
final String inputText;
const MainButtonPressedEvent(this.inputText);
}
UI part
import 'package:bloc_test/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: BlocProvider(
create: (context) => MainBloc(),
child: SubWidget(),
),
),
);
}
}
class SubWidget extends StatelessWidget {
TextEditingController inputText = TextEditingController();
String? exportText;
#override
Widget build(BuildContext context) {
MainBloc mainBloc = BlocProvider.of<MainBloc>(context);
return BlocBuilder<MainBloc, MainState>(
builder: (context, state) {
if (state is MainCalculatedState) {
exportText = state.exportText;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${exportText ?? ''} data'),
SizedBox(
width: 200,
child: TextField(
controller: inputText,
),
),
ElevatedButton(
onPressed: () =>
mainBloc.add(MainButtonPressedEvent(inputText.text)),
child: const Text('Button')),
],
),
);
},
);
}
}
Equatable is used to make it easy for you to program, how and when states are the same (no update) and when they are different (update).
Your updates do not work because you are sending the same state repeatedly, but you did not tell the Equatable extension how to find out if they are different. So they are all the same.
So to make sure your program understands that some states of the same kind are indeed different and should cause an update, you need to make sure you mention what makes them different:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
// this tells the Equatable base class to consider your text property
// when trying to figure out if two states are different.
// If the text is the same, the states are the same, so no update
// If the text is different, the states are different, so it will update
#override
List<Object> get props => [this.exportText];
}
If you remove Equatable altogether, two newly instanciated states are never equal, so that would solve your problem as well... except that at some point you will want them to be, and then you need to add it back in.
Your MainCalculatedState needs to override the props getter from Equatable and return the list of all properties which should be used to assess equality. In your case it should return [exportText].
Example:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
#override
List<Object> get props => [exportText];
}
Related
Introduction: I have been working on this problem for a while. The problem involves updating the state of deeply nested data with the Bloc 8.0.0+ paradigm. I am using a class that holds deeply nested data in it (in this minimum viable code: a ‘Collection’ that contains a name and children are meant to be a Show Series, and the Series has children that are meant to be Seasons, and Seasons have children that are meant to be Episodes).
The nested structure is something like this:
List<CollectionState>
|-- List<CollectionState>
| |-- List<CollectionState>
| | |-- List<CollectionState>
An important functionality of the code is that a child is added to the children list of the correct parent so it will display in the correct order of the hierarchy of parents to their children in the ListView; i.e. Collection has one Series (8768), and that Series has two Seasons (1817 and 7623), and when pressing on a Season, an Episode is added to its correct parent Season instead of being added to the bottom of the ListView. In this case, pressing on Season 1817 four times adds Episodes 2175, 2773, 5420 and 8826 under itself instead of adding to Season 7623.
Problem: As I understand it, a good practice while working with BLoC 8.0.0+ would be extending the CollectionState class with Equatable. The following code I provide works; however, it does not use this best practice. I want it to do so, but I am having problems which I will explain shortly. I have commented in the code of collection_state.dart with:
// 1) where Equatable should be extended
I have located in the code where the issue occurs in collection_bloc.dart when the CollectionState class is extended with Equatable (please note that the problem happens only with changing the code by extending CollectionState class with Equatable, which the code does not do). I have commented in the code with this comment:
//TODO: Here is the problem. This code does not work properly when I extend the CollectionState class to Equatable.
Surprisingly, there is little information online that I could find about using deeply nested data with BLoC 8.0.0+.
I am new to BLoC 8.0.0+ and even newer to Equatable (I’ve always used Provider up to this point) and I don’t understand why my code is not updating correctly when extending Equatable. I guess I am having an immutability issue because the update to the class with the AddInfo bloc event is not considered different to Equatable. I am at a loss to understand how to change my code to use the best practices with deeply nested data with BLoC.
Question:
How do I change my code to extend the CollectionState class with Equatable and still have it update my UI correctly?
Bearing in mind that I have a cursory understanding of Equatable, I would like to know more about the underlying root of the problem. Is it the case that the bloc event method is not producing a class different enough to Equatable, so it is not updating or is something entirely different happening here?
Note: When I extend Equatable and add the props for name, children, showType; and click on the collection, it adds Series as normal. But, when I click on Series, the app does not update to show the addition of the Seasons and Episodes. However, when I press Hot Reload, the app is refreshed with all the correct items. I believe that it is not comparing the nested objects, and I don't know how to allow it to compare them with my code.
pub spec.yaml
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.1
equatable: ^2.0.5
main.dart
import 'package:deeply_nested_objects/bloc/add_to_collection_logic.dart';
import 'package:deeply_nested_objects/bloc/collection_bloc.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CollectionBloc(),
child: const MaterialApp(
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return BlocBuilder<CollectionBloc, CollectionState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Deeply nested data and Bloc 8.0.0+'),
),
body: ListView.builder(
itemCount: state.getAllNodes(state).length,
itemBuilder: (context, index) {
var nodes = state.getAllNodes(state)[index];
Color textColor = getColor(nodes);
double distance = getPaddingDistance(nodes);
return Padding(
padding: EdgeInsets.only(left: distance),
child: ListTile(
onTap: () => addToCollectionLogic(nodes.showType, index,
nodes.children.length + 1, context),
leading: Card(
child: Text(nodes.name, style: TextStyle(color: textColor)),
),
),
);
},
),
);
},
);
}
double getPaddingDistance(CollectionState nodes) {
switch (nodes.showType) {
case ShowType.collection:
return 0;
case ShowType.series:
return 20;
case ShowType.season:
return 40;
case ShowType.episode:
return 60;
}
}
Color getColor(CollectionState nodes) {
switch (nodes.showType) {
case ShowType.collection:
return Colors.black;
case ShowType.series:
return Colors.blue;
case ShowType.season:
return Colors.green;
case ShowType.episode:
return Colors.red;
}
}
}
add_collection_logic.dart
import 'package:deeply_nested_objects/bloc/collection_bloc.dart';
import 'package:deeply_nested_objects/bloc/collection_event.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void addToCollectionLogic(
ShowType showType, int index, int count, BuildContext context) {
void passToBloc(String name, ShowType showType) =>
BlocProvider.of<CollectionBloc>(context).add(
AddInfo(
index: index,
child: CollectionState(name: name, showType: showType, children: []),
),
);
switch (showType) {
case ShowType.collection:
passToBloc('Series $count', ShowType.series);
break;
case ShowType.series:
passToBloc('Season $count', ShowType.season);
break;
case ShowType.season:
passToBloc('Episode $count', ShowType.episode);
break;
case ShowType.episode:
break;
}
}
collection_event.dart
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:equatable/equatable.dart';
abstract class CollectionEvents extends Equatable {
#override
List<Object> get props => [];
}
class AddInfo extends CollectionEvents {
AddInfo({required this.index, required this.child});
final int index;
final CollectionState child;
}
collection_bloc.dart
import 'package:deeply_nested_objects/bloc/collection_event.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CollectionBloc extends Bloc<CollectionEvents, CollectionState> {
CollectionBloc() : super(CollectionState.initial()) {
on<AddInfo>((event, emit) {
if (event.child.showType == ShowType.series) {
emit(state.copyWith(children: [...state.children, event.child]));
}
if (event.child.showType == ShowType.season ||
event.child.showType == ShowType.episode) {
//TODO: Here is the problem. This code does not work properly when I extend the CollectionState class to Equatable.
// get the list of all nodes
List<CollectionState> list = state.getAllNodes(state);
// find the parent node while still in the list
CollectionState parent = list[event.index];
// add the child to the parent
parent.children.add(event.child);
// update the state
emit(state.copyWith(children: [...state.children]));
}
});
}
}
collection_state.dart
enum ShowType { collection, series, season, episode }
// 1) where should be Equatable
class CollectionState {
const CollectionState({
required this.name,
required this.children,
required this.showType,
});
final String name;
final List<CollectionState> children;
final ShowType showType;
factory CollectionState.initial() {
return const CollectionState(
name: "Collection",
showType: ShowType.collection,
children: [],
);
}
List<CollectionState> getAllNodes(CollectionState node) {
// empty list to store the result
List<CollectionState> result = [];
// add the current node
result.add(node);
// add the children too
for (CollectionState child in node.children) {
// composite design pattern seek and find
result.addAll(getAllNodes(child));
}
return result;
}
CollectionState copyWith({
String? name,
List<CollectionState>? children,
ShowType? showType,
}) {
return CollectionState(
name: name ?? this.name,
children: children ?? this.children,
showType: showType ?? this.showType,
);
}
}
You have to assign properties in equatable that you want to check equality on.
Example:
class SomeClass extends Equatable {
SomeClass({required this.index});
final int index;
#override
List<Object> get props => [];
}
If I would check some SomeClass(index: 10) == SomeClass(index: 9) it would be true because I didn't say equatable what properties it should look for on == operator
If I update my code to
class SomeClass extends Equatable {
SomeClass({required this.index});
final int index;
#override
List<Object> get props => [index];
}
Now same check would be false, cause it is looking on index property
I'm trying to test a widget that makes use of a bloc. I'd like to be able to emit states from my mocked bloc in response to events being fired by the widget under test. I've tried a number of approaches without success. I'm not sure if I'm making some simple error or if I'm approaching the problem all wrong.
Here is a simplified project which demonstrates my issue. (the complete code for this can be found at https://github.com/andrewdixon1000/flutter_bloc_mocking_issue.git)
very simple bloc
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(FirstState());
#override
Stream<MyState> mapEventToState(
MyEvent event,
) async* {
if (event is TriggerStateChange) {
yield SecondState();
}
}
}
#immutable
abstract class MyEvent {}
class TriggerStateChange extends MyEvent {}
#immutable
abstract class MyState {}
class FirstState extends MyState {}
class SecondState extends MyState {}
My widget under test
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/my_bloc.dart';
import 'injection_container.dart';
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
_FirsPageState createState() => _FirsPageState();
}
class _FirsPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => serviceLocator<MyBloc>(),
child: Scaffold(
appBar: AppBar(title: Text("Page 1")),
body: Container(
child: BlocConsumer<MyBloc, MyState>(
listener: (context, state) {
if (state is SecondState) {
Navigator.pushNamed(context, "SECONDPAGE");
}
},
builder: (context, state) {
if (state is FirstState) {
return Column(
children: [
Text("State is FirstState"),
ElevatedButton(
onPressed: () {
BlocProvider.of<MyBloc>(context).add(TriggerStateChange());
},
child: Text("Change state")),
],
);
} else {
return Text("some other state");
}
},
),
),
),
);
}
}
my widget test
This is where I'm struggling. What I'm doing is loading the widget and then tapping the button. This causes the widget to add an event to the bloc. What I want to be able to do is have my mock bloc emit a state in response to this, such that the widget's BlocConsumer's listener will see the state change the navigate. As you can see from the comment in the code I've tried a few things without luck. Current nothing I've tried results in the listener seeing a state change.
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart' as mocktail;
import 'package:get_it/get_it.dart';
import 'package:test_bloc_issue/bloc/my_bloc.dart';
import 'package:test_bloc_issue/first_page.dart';
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
class FakeMyState extends Fake implements MyState {}
class FakeMyEvent extends Fake implements MyEvent {}
void main() {
MockMyBloc mockMyBloc;
mocktail.registerFallbackValue<MyState>(FakeMyState());
mocktail.registerFallbackValue<MyEvent>(FakeMyEvent());
mockMyBloc = MockMyBloc();
var nextScreenPlaceHolder = Container();
setUpAll(() async {
final di = GetIt.instance;
di.registerFactory<MyBloc>(() => mockMyBloc);
});
_loadScreen(WidgetTester tester) async {
mocktail.when(() => mockMyBloc.state).thenReturn(FirstState());
await tester.pumpWidget(
MaterialApp(
home: FirstPage(),
routes: <String, WidgetBuilder> {
'SECONDPAGE': (context) => nextScreenPlaceHolder
}
)
);
}
testWidgets('test', (WidgetTester tester) async {
await _loadScreen(tester);
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// What do I need to do here to mock the state change that would
// happen in the real bloc when a TriggerStateChange event is received,
// such that the listener in my BlocConsumer will see it?
// if tried:
// whenListen(mockMyBloc, Stream<MyState>.fromIterable([SecondState()]));
// and
// mocktail.when(() => mockMyBloc.state).thenReturn(SecondState());
await tester.pumpAndSettle();
expect(find.byWidget(nextScreenPlaceHolder), findsOneWidget);
});
}
I took a look and opened a pull request with my suggestions. I highly recommend thinking of your tests in terms of notifications and reactions. In this case, I recommend having one test to verify that when the button is tapped, the correct event is added to the bloc (the bloc is notified). Then I recommend having a separate test to ensure that when the state changes from FirstState to SecondState that the correct page is rendered (the UI reacts to state changes appropriately). In the future, I highly recommend taking a look at the example apps since most of them are fully tested.
I followed the BloC tutorial and did according to them, I have a Bloc which has this method
#override Stream<FridgeState> mapEventToState(FridgeEvent event) async* { .. }
where FridgeEvent is an abstract class
abstract class FridgeEvent {
const FridgeEvent();
}
class CreateFridgeEvent extends FridgeEvent {
final double lat, lng;
CreateFridgeEvent({required this.lat, required this.lng});
}
class DeleteFridgeEvent extends FridgeEvent {}
When instantiating the bloc within the widget and passing in a subclass of FridgeEvent, I get an error that the parameter type can't be assigned. What am I doing wrong?
Upon request, here's the code of the bloc
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:foodey_flutter/domain/fridge/entity/Fridge.dart';
import 'package:foodey_flutter/domain/fridge/usecase/CreateFridgeUseCase.dart';
import 'package:foodey_flutter/factory/FridgeFactory.dart';
import 'package:foodey_flutter/ui/fridge/create//event.dart';
import 'package:foodey_flutter/ui/fridge/create//state.dart';
class FridgeBloc extends Bloc<FridgeEvent, FridgeState> {
CreateFridgeUseCase? createFridgeUseCase;
FridgeBloc(FridgeState initialState) : super(initialState) {
FridgeFactory.inject(this);
}
#override
Stream<FridgeState> mapEventToState(FridgeEvent event) async* {
if (event is CreateFridgeEvent) {
Fridge? result = await createFridgeUseCase?.execute(event.lat, event.lng);
if (result != null)
yield FridgeSuccessfullyLoadedState(result);
else
yield FridgeErrorState(
exception: Exception("Failed to create a fridge"));
} else {
yield FridgeErrorState(exception: Exception("Operation not supported"));
}
}
}
Here's the code of the widget
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:foodey_flutter/ui/fridge/create/FridgeBloc.dart';
import 'package:foodey_flutter/ui/fridge/create/event.dart';
class CreateFridgeWidget extends StatefulWidget {
#override
_CreateFridgeState createState() => _CreateFridgeState();
}
class _CreateFridgeState extends State<CreateFridgeWidget> {
late FridgeBloc _bloc;
#override
void initState() {
super.initState();
this._bloc = BlocProvider.of<FridgeBloc>(context);
}
#override
Widget build(BuildContext context) {
return Container(
key: widget.key,
alignment: Alignment.center,
padding: EdgeInsets.all(8),
child: Column(
children: [
Text("Are you sure you want to create your fridge?"),
Row(
children: [
TextButton(
key: widget.key,
onPressed: () {
_bloc.add(CreateFridgeEvent(lat: 0, lng: 1));
},
child: Text("Yes"),
),
TextButton(
key: widget.key,
onPressed: () => {},
child: Text("No"),
),
],
)
],
),
);
}
}
And here are my defined events
abstract class FridgeEvent {
const FridgeEvent();
}
class CreateFridgeEvent extends FridgeEvent {
final double lat, lng;
CreateFridgeEvent({required this.lat, required this.lng});
}
class DeleteFridgeEvent extends FridgeEvent {}
Update: After copy pasting the code here and checking for completeness I found the issue. For anyone facing the same: the import of the event within the bloc code contains two /'s within the path. After removing one / the compiler didn't complain anymore that the wrong type is being passed. No idea how this second / was added and why the compiler didn't complain at all about that. Apparently, the objects defined in ..//event.dart are not the same when imported from ../event.dart
After copy pasting the code here and checking for completeness I found the issue. For anyone facing the same: the import of the event within the bloc code contains two /'s within the path. After removing one / the compiler didn't complain anymore that the wrong type is being passed. No idea how this second / was added and why the compiler didn't complain at all about that.
Apparently, the objects defined in ..//event.dart are not the same when imported from ../event.dart
So, I tried to learn flutter especially in BLoC method and I made a simple ToggleButtons with BLoC. Here it looks like
ToggleUI.dart
class Flutter501 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 50 With Bloc Package',
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocProvider<ToggleBloc>(
builder: (context) => ToggleBloc(maxToggles: 4),
child: MyToggle(),
)
],
),
),
),
);
}
}
class MyToggle extends StatelessWidget {
const MyToggle({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
ToggleBloc bloc = BlocProvider.of<ToggleBloc>(context);
return BlocBuilder<ToggleBloc, List<bool>>(
bloc: bloc,
builder: (context, state) {
return ToggleButtons(
children: [
Icon(Icons.arrow_back),
Icon(Icons.arrow_upward),
Icon(Icons.arrow_forward),
Icon(Icons.arrow_downward),
],
onPressed: (idx) {
bloc.dispatch(ToggleTap(index: idx));
},
isSelected: state,
);
},
);
}
}
ToogleBloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
abstract class ToggleEvent extends Equatable {
const ToggleEvent();
}
class ToggleTap extends ToggleEvent {
final int index;
ToggleTap({this.index});
#override
// TODO: implement props
List<Object> get props => [];
}
class ToggleBloc extends Bloc<ToggleEvent, List<bool>> {
final List<bool> toggles = [];
ToggleBloc({
#required int maxToggles,
}) {
for (int i = 0; i < maxToggles; i++) {
this.toggles.add(false);
}
}
#override
// TODO: implement initialState
List<bool> get initialState => this.toggles;
#override
Stream<List<bool>> mapEventToState(ToggleEvent event) async* {
// TODO: implement mapEventToState
if (event is ToggleTap) {
this.toggles[event.index] = !this.toggles[event.index];
}
yield this.toggles;
}
}
The problem came when I tried to Tap/Press one of the buttons, but it doesn't want to change into the active button. But it works whenever I tried to press the "Hot Reload". It likes I have to make a setState whenever the button pressed.
The BlocBuilder.builder method is only executed if the State changes. So in your case the State is a List<bool> of which you only change a specific index and yield the same object. Because of this, BlocBuilder can't determine if the List changed and therefore doesn't trigger a rebuild of the UI.
See https://github.com/felangel/bloc/blob/master/docs/faqs.md for the explanation in the flutter_bloc docs:
Equatable properties should always be copied rather than modified. If an Equatable class contains a List or Map as properties, be sure to use List.from or Map.from respectively to ensure that equality is evaluated based on the values of the properties rather than the reference.
Solution
In your ToggleBloc, change the List like this, so it creates a completely new List object:
#override
Stream<List<bool>> mapEventToState(ToggleEvent event) async* {
// TODO: implement mapEventToState
if (event is ToggleTap) {
this.toggles[event.index] = !this.toggles[event.index];
this.toggles = List.from(this.toggles);
}
yield this.toggles;
}
Also, make sure to set the props for your event, although it won't really matter for this specific question.
BlocBuilder will ignore the update if a new state was equal to the old state. When comparing two lists in Dart language, if they are the same instance, they are equal, otherwise, they are not equal.
So, in your case, you would have to create a new instance of list for every state change, or define a state object and send your list as property of it.
Here is how you would create new list instance for every state:
if (event is ToggleTap) {
this.toggles[event.index] = !this.toggles[event.index];
}
yield List.from(this.toggles);
You can read more about bloc library and equality here:
https://bloclibrary.dev/#/faqs?id=when-to-use-equatable
I am unable to access a public static boolean from a different class, eg. I have a boolean isFull in my StudyjiosListviewScreen class as shown:
class StudyjiosListviewScreen extends StatefulWidget {
#override
_StudyjiosListviewScreenState createState() => _StudyjiosListviewScreenState();
}
class _StudyjiosListviewScreenState extends State<StudyjiosListviewScreen> {
static bool isFull = false;
...
I want to use this boolean isFull in another class JoinStudyjio.
I created an instance of the StudyjiosListviewScreen class in the JoinStudyjio class like this:
StudyjiosListviewScreen listviewScreen = StudyjiosListviewScreen();
But when I try to use the boolean isFull like this:
if (listviewScreen.isFull) {
...
I get an error. I have already imported the file for the StudyjiosListviewScreen class inside the file for the JoinStudyjio class.
This is because StudyjiosListviewScreen and _StudyjiosListviewScreenState are 2 different classes.
The static variable isFull which you are trying to access is of the later one and you are trying to access it by creating an instance of the first one. If it had been a static variable of the class StudyjiosListviewScreen, you could have accessed it without even creating an instance of that class like this StudyjiosListviewScreen.isFull
If I understood your issue correctly, and following the suggestion I made in my comment, here is a code example of sharing a variable and a method to change it's value, down to two classes from a parent class:
class VariableSharing62951032 extends StatefulWidget {
#override
_VariableSharing62951032State createState() => _VariableSharing62951032State();
}
class _VariableSharing62951032State extends State<VariableSharing62951032> {
bool isFull = false;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ClassA62951032(isFull: isFull, swapIsFull: swapIsFull,),
ClassB62951032(isFull: isFull, swapIsFull: swapIsFull,),
],
);
}
void swapIsFull(){
setState(() {
isFull = !isFull;
});
}
}
class ClassA62951032 extends StatefulWidget {
final bool isFull;
final Function swapIsFull;
ClassA62951032({
this.isFull,
this.swapIsFull
});
#override
_ClassA62951032State createState() => _ClassA62951032State();
}
class _ClassA62951032State extends State<ClassA62951032> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Class A'),
Text(widget.isFull.toString()),
RaisedButton(
child: Text('Swap isFull'),
onPressed: () => widget.swapIsFull(),
),
],
);
}
}
class ClassB62951032 extends StatefulWidget {
final bool isFull;
final Function swapIsFull;
ClassB62951032({
this.isFull,
this.swapIsFull
});
#override
_ClassB62951032State createState() => _ClassB62951032State();
}
class _ClassB62951032State extends State<ClassB62951032> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Class B'),
Text(widget.isFull.toString()),
RaisedButton(
child: Text('Swap isFull'),
onPressed: () => widget.swapIsFull(),
),
],
);
}
}
Sharing variables and methods between classes it's a huge deal in Flutter.
First of all, you are passing it in the wrong way. That variable is saved in your state widget, which is defined as private.
So, or you define it as public and than you pass a key associated with your state, or you change complitelly approach. I don't like passing keys and it is not good for production, so I will give you a better example using providers:
add provider library to your pubspec.yaml:
provider: ^4.3.1 // Or latest version
Create a class where you can save that value:
class valuesHelper {
//In this class we are storing global, dynamic values
bool _isSeen;
valuesHelper() {
this._isSeen = false;
}
void setValue(bool value) {
this._isSeen = value;
}
bool getValue(){
return this._isSeen;
}
}
Now wrap your main with the provider and pass the valuesHelper();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => valuesHelper(),
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
Now call the Provider.of(context) wherever you want.
//Somwhere in your code when you have access to context:
ValueHelper helper = Provider.of<valueHelper>(context);
helper.setValue(true);
//Somwhereelse in your code when you have access to context:
ValueHelper helper = Provider.of<valueHelper>(context);
bool theValueIWant = helper.getValue();
If you have asynchronous stuff and huge state managment Blocs are even better and fancier, but for this kind of things Providers are more than enough.