Flutter: In RiverPod , how to alter state model's variables directly? - flutter

I am currently learning River Pod and also new to flutter.
When setting new state in StateNotifer , I need to create a new model and replace the state
But directly changing is not working
class CounterModel {
CounterModel(this.count, this.age);
int count;
int age;
}
class CounterNotifier extends StateNotifier<CounterModel> {
CounterNotifier() : super(_initialValue);
static CounterModel _initialValue = CounterModel(0,18);
void increment() {
// state.count = state.count + 1; // not working, but need like this !
state = CounterModel(state.count + 1, state.age); // working
}
}
In the above code , when I trying to change the count variable directly like, state.count = state.count + 1 , nothing changed
But when reinitialising the state by creating a new model like state = CounterModel(state.count + 1, state.age)
Its seems to be state model variables are immutable and needs to be recreated on every alteration !
My question is , what if the CounterModel have 50 variables , then I have to do something like
state = CounterModel (var1,var2,......,var49,var50) ;
So , is it possible to directly change the variables like
state.var1 = new_value1;
state.var2 = new_value2;
....
state.var50 = new_value50;

You have to always reassign the state in StateNotifier for Consumers to see the changes hence, state.var1 = new_value1; can't work with StateNotifier
If You're very keen about that syntax, use ChangeNotifier since it allows you change individual properties of the class but you must call notifyListeners
Like so:
class CounterNotifier extends StateNotifier {
static CounterModel value = CounterModel(0,18);
void increment() {
value.count = value.count + 1;
notifyListeners();
}
}
If You want to stick with StateNotifier and don't want to write boilerplate code, create a copyWith method on the model.
Like so:
class CounterModel {
CounterModel(this.count, this.age);
int count;
int age;
CounterModel copyWith({int? count, int? age}){
return CounterModel(
count ?? this.count,
age ?? this.age
);
}
}
Then you can keep reassigning with it like so:
class CounterNotifier extends StateNotifier<CounterModel> {
CounterNotifier() : super(_initialValue);
static CounterModel _initialValue = CounterModel(0,18);
void increment() {
state = state.copyWith(count: state.count + 1);
}
}

Related

Flutter Provider integer Reset

I have a count variable like this:
class Count with ChangeNotifier {
int _count = 1;
int get count => _count;
void setCount(bool isIncrement) {
if (isIncrement) {
_count = checkCount(_count + 1);
} else {
_count = checkCount(_count - 1);
print("decrement" + _count.toString());
}
notifyListeners();
}
int checkCount(int count) {
if (count < 0) {
return 0;
} else if (count > 10) {
return 10;
} else {
return count;
}
}
}
I'm using the provider to use it on the food pages. But when I switch between the pages, the count variable continues from where it left off. I want it to restart as 1 every time the page changes.
class Hamburger extends StatefulWidget {
const Hamburger({super.key, required Count count});
#override
State<Hamburger> createState() => _HamburgerState();
}
class _HamburgerState extends State<Hamburger> {
#override
Widget build(BuildContext context) {
Color coloricon = Provider.of<iconcolor>(context).coloricon;
int count = Provider.of<Count>(context).count;
return Scaffold(
…
);
}
}
The idea of Provider is to have a consistent state across all your pages.
If you want to save only the number of hamburgers selected you need a variable similar to _hamburgerCount and control it there.
Or, for a more scalable solution, you can create a Map<String, int> or something similar to store the amount of each type. There is many ways to solve this, but with one global count variable you can not achieve storing a count for each type of food.

My boolean bloc state variable stops changing after the first reassignment

I am relatively new at flutter. I am creating a timer app using bloc. In the timer you are supposed to have a session, then a break and so on and so forth. To track which type of session to start(a break or session), I am using a boolean value isBreak which I am tracking in the SessionsBloc.
Here is the definition of the SessionsState:
part of 'sessions_bloc.dart';
abstract class SessionsState extends Equatable { final bool isBreak; final int sessionCount; const SessionsState(this.isBreak, this.sessionCount);
#override List<Object> get props => []; }
class SessionsInitial extends SessionsState { const SessionsInitial(super.isBreak, super.sessionCount); }
class SessionTrackingState extends SessionsState { const SessionTrackingState(super.isBreak, super.sessionCount); }
I then use A BlocListener to check for the TimerFinishedState from another bloc TimerBloc, after which I add an event, SessionTrackingEvent, that is supposed to change the aforementioned boolean value.
Here is the code for the listener:
listener: (context, state) {
Task currentTask =
BlocProvider.of<TasksBloc>(context).state.currentTask;
bool isTimeBoxed = currentTask.isTimeBoxed;
int sessionDuration;
int breakDuration;
if (state is TimerCompleteState) {
//Get the SessionsBloc state
final sessionState = BlocProvider.of<SessionsBloc>(context).state;
//Get the current value of the isBreak boolean value
bool isBreak = sessionState.isBreak;
int sessionCount = sessionState.sessionCount;
//Print statements: Can't Debug properly yet:(
print(sessionState.isBreak);
print(sessionState.sessionCount);
if (isTimeBoxed) {
sessionDuration = currentTask.sessionTimeBox!;
breakDuration = currentTask.breakTimeBox ?? 2;
// sessionCount = HiveDb().getTaskSessionCount(currentTask.taskName);
} else {
sessionDuration = 5;
breakDuration = 3;
}
if (isBreak) {
//Set timer with duration time
BlocProvider.of<TimerBloc>(context)
.add(InitializeTimerEvent(duration: sessionDuration));
//Add Event to track session count and next countdown if break or session
BlocProvider.of<SessionsBloc>(context).add(SessionTrackingEvent(
isBreak: isBreak,
sessionCount: sessionCount,
));
} else {
//Add event to reset timer
BlocProvider.of<TimerBloc>(context)
.add(InitializeTimerEvent(duration: breakDuration));
//Emit a state that notifies Button Bloc that it's a break and deactivate repeat button.
BlocProvider.of<TimerBloc>(context)
.add(OnBreakEvent(duration: breakDuration));
//Add Event to track session count and next countdown if break or session
BlocProvider.of<SessionsBloc>(context).add(SessionTrackingEvent(
isBreak: isBreak,
sessionCount: sessionCount += 1,
));
}
}
},
Finally, in the SessionsBloc, I only have super constructor which initializes the boolean value to false and one event handler that is supposed to change it as appropriate.
class SessionsBloc extends Bloc<SessionsEvent, SessionsState> {
SessionsBloc() : super(const SessionsInitial(false, 0)) {
on<SessionTrackingEvent>((event, emit) {
emit(SessionTrackingState(
event.isBreak ? false : true, event.sessionCount));
});
}
}
The expected result is that for each SessionTrackingEvent added, the boolean should be toggled to the opposite value. However, what actually happens is that it Works the first time, turning the initialized value of false to true and from there it just stays the same. Here is a screenshot of my print statement which outputs the value of IsBreak after every call to SessionTrackingEvent.
Here is a screenshot of my print statement which outputs the value of IsBreak after every call to SessionTrackingEvent.
I have tried changing the variable type from final because I thought maybe it's a flutter constraint about reassigning variables.
I have tried moving the reading of the block state value into the build method outside of the listener because I thought maybe it doesn't read the value as frequently.
What could be the problem, what might be preventing the value from changing as appropriate?
You forgot to pass your SessionsState properties into props list, so the Bloc can't differentiate between old and new states without it.
abstract class SessionsState extends Equatable {
final bool isBreak;
final int sessionCount;
const SessionsState(this.isBreak, this.sessionCount);
#override
List<Object> get props => [isBreak, sessionCount]; // your props should go here like this
}

what is Get.create() in the Getx package and what it does

reading the Getx package documentation, I faced this method:
Get.create<ShoppingController>(() => ShoppingController());
and it says:
Get.create(()=>Controller()) will generate a new Controller each time you call Get.find(),
but, I don't seem to understand what this means and how it differs from the Get.put() and Get.lazyPut().
I found the answer for that question.
The big difference between
Get.create<ShoppingController>(() => ShoppingController());
And :
Get.put(ShoppingController());
Both of them are used in injecting dependencies in the a Flutter app, But Get.put<T>(T()) injects it just one time and whenever we call Get.find<T>() it looks up for that exact dependency and return it, so we can rememeber this:
Get.put<T>(()) inject a dependency and whatever time we call Get.find<T>() across the entire app, the same T is returned.
In the other side, Get.create<V>(() => V) also inject a dependency in a Flutter app, But every time we call Get.find<V>(), it doesn't return the same V, it creates a new instance of V, then return it, so we can rememeber this:
Get.create<V>(() => V) don't return the same instance, it creates a new one every time Get.find<V>() is called.
Get.put(T()) :
class ControllerOne extends GetxController {
int number = 10;
increment() {
number += 10;
}
}
final controllerOne = Get.put<ControllerOne>(ControllerOne());
final controllerOneFinder = Get.find<controllerOne>();
controllerOneFinder.increment();
final controllerOneSecondFinder = Get.find<controllerOne>();
print(controllerOneFinder.number); // 20
print(controllerOneSecondFinder.number); // 20
it stay the same.
Get.create(() =>T) :
class ControllerTwo extends GetxController {
int secondNumber = 10;
increment() {
secondNumber += 10;
}
}
final controllerTwo = Get.create<ControllerTwo>(() => (ControllerTwo());
final controllerTwoFinder = Get.find<ControllerTwo>();
controllerTwoFinder.increment();
final controllerTwoSecondFinder = Get.find<ControllerTwo>();
print(controllerTwoFinder.number); // 20
print(controllerTwoSecondFinder.number); // 10
Each one is different than the other. (controllerTwoSecondFinder == controllerTwoFinder) is false.

How to make model fields observable in Flutter

I have a model called category , which has two fields, User has a list of categories and what to update the category name, for State Management getx is used
class Category {
String? categoryName;
bool status;
Category(this.categoryName,this.status);
}
I have a observable list called catList which is used in List widget
var catList = <Category>[].obs;
when I update the category field it doest not update
catList[index].categoryName = categoryChangedName.value;
but If I update the item in object and then assign the object to catList then It changed
catList[index] = Category(categoryChangedName.value, catList[index].status );
My question is how to make model fields observable, if we have more fields changes then this is not proper way.
As of GetX documentation you need to update values using method and call update(); method inside custom object !
Ex:
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // look here!
}
}
Your use case might be like....
class Category {
String? categoryName;
bool status;
Category(this.categoryName,this.status);
void updateCategoryName(name){
this.categoryName = name;
update();
}
}
//Use like..
catList[index].updateCaetgoryName = categoryChangedName.value;

Flutter Bloc Pattern passing more than just state?

I was reading that flutter bloc pattern is best for managing state.
I can see the need to separate display and business logic into separate areas.
Trying to learn flutter/dart from ground up.
I building a listview and each row of individual items that acts as a shopping card. User can select # of items or flavors of bagels in each row.
first issue is how to display the count of variable _dozen on screen? I have cubit/bloc sample code working. Trying to add simple test logic to when use select intervals of 13 bagels it increment a count of 1 bakers = _dozen. I can see this in debugger works . But I cannot ( sorry being new ) cannot figure out how to pass the _dozen variable to my view_page. Is passing in Bloc on for state management correct? or do I fall back and try inheritabve widget?
at end of day just want to display at bottom of screen total dozen bagels order.
import 'package:bloc/bloc.dart';
/// {#template counter_cubit}
/// A [Cubit] which manages an [int] as its state.
/// {#endtemplate}
class CounterCubit extends Cubit<int> {
/// {#macro counter_cubit}
CounterCubit() : super(12);
int _dozen = 12;
/// Add 1 to the current state.
void increment() {
if (state >= 0) {
emit(state + 1);
_dozen = ( state ~/ 13);
print('dozen:' + '$_dozen');
}
}
/// Subtract 1 from the current state.
void decrement() {
if (state > 0) {
emit(state - 1);
_dozen = ( state ~/ 13);
print('dozen:' + '$_dozen');
}
}
}
It seems that you need to keep more than one int in your state; one for count and one for dozen. Make a separate class CounterState to represent your state and hold those values so you end up with this code:
import 'package:bloc/bloc.dart';
class CounterState {
int count;
int dozen;
CounterState(this.count, this.dozen);
}
/// {#template counter_cubit}
/// A [Cubit] which manages an [CounterState] as its state.
/// {#endtemplate}
class CounterCubit extends Cubit<CounterState> {
/// {#macro counter_cubit}
CounterCubit() : super(CounterState(12,1));
/// Add 1 to the current state.
void increment() {
if (state.count >= 0) {
final int count = state.count + 1;
emit(CounterState(count, count ~/ 12));
print('dozen:' + '${state.dozen}');
}
}
/// Subtract 1 from the current state.
void decrement() {
if (state.count > 0) {
final int count = state.count - 1;
emit(CounterState(count, count ~/ 12));
print('dozen:' + '${state.dozen}');
}
}
}
Then in your Widgets, access this as state.count and state.dozen instead of just state.
This solution can be improved, but it is sufficient to get you going.