GetxController structure - flutter

class Controller extends GetxController {
int currentIdx = ''.obs;
void updateInt(int idx) {
currentIdx = idx.obs;
}
}
here is my controller. Now I put this line a widget to get idx value and assign immediately to currentIdx value:
Controller().updateInt(idx);
but .obs lines gives error in this current structure.
when I change the controller:
class Controller extends GetxController {
RxInt currentIdx = 0.obs;
void updateInt(RxInt idx) {
currentIdx = idx;
}
}
this time Controller().updateInt(idx); gives error. (RxInt not compatible int value)
how can I handle this?

You need to declare and use like that:
RxInt currentIdx = 0.obs;
void updateInt(int id) {
currentIdx.value = id;
}

Related

Flutter testing: No GoRouter found in context

I want to test my ViewModel that goes to a named route with GoRouter. Therefore, I pass my context from my View to the ViewModel. My problem is that the mocked contains does not contain a GoRouter. I get the error "No GoRouter found in context". How do I mock the context so that I can unit test this?
void main() {
group("MainScreenViewModel Tests", () {
MainScreenViewModel viewModel = MainScreenViewModel();
setUp(() {
viewModel = MainScreenViewModel();
});
test("MoveToTab should change currentIndex value", () {
final context = MockBuildContext();
when(() => context.goNamed("Test")).thenReturn(null); // Here is the problem
viewModel.moveToTab(context, index: 1);
expect(viewModel.currentIndex, 1);
verify(() => context.goNamed(any())).called(1);
verify(() => notifyListenerCallback()).called(1);
});
});
}
The ViewModel
const List<String> routeList = ["/a", "/b"];
class MainScreenViewModel extends ChangeNotifier {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
void moveToTab(BuildContext context, {required int index}) {
_currentIndex = index;
context.goNamed(routeList[index]);
notifyListeners();
}
}

Rivepod - create StateNotifier with parameter in input

from te first page of Rivepod library I found this example.
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
return Counter();
});
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() => state++;
}
Now, what I want to ask is: what is the right way to do something like this below?
class Counter extends StateNotifier<int> {
Counter({int? value}) : super(value ?? 0);
void increment() => state++;
}
I want that my counterProvider can have optional parameters in input, my use case is a StateNotifier used to manage a edit/create page.
You can do like this:
final valueProvider = StateProvider<Counter, int>((ref) => return 5);
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
final value = ref.watch(valueProvider);
return Counter(value);
});
class Counter extends StateNotifier<int> {
Counter(int value) : super(value ?? 0);
void increment() => state++;
}
Also, you can try using the modifier .family
That change state provider you can try like this:
ref.read(valueProvider.notifier).update((state) => 212);
// or use
...
ProviderScope(
overrides: [
valueProvider.overrideWithValue(21332432),
],
child: MyApp(),
),
...
You can see about it here.
to have an optional parameter, this is how to do it:
class Counter extends StateNotifier<int> {
Counter({int value = 0}) : super(value);
void increment() => state++;
}

Provider: How to notify listeners of A from B

I have two controllers using the changenotifiers in my app and I want to notify listeners of A from B. I have be trying two different methods for the same.
Method 1
(Controller A):
class DrawController extends ChangeNotifier {
void clearCanvas() {
selectedText = '';
shouldClearCanvas = true;
currentDrawPoints.initialPoint = null;
currentDrawPoints.endPoint = null;
print(currentDrawPoints.initialPoint);
notifyListeners();
}
}
Calling it from (Controller B):
class GameController extends ChangeNotifier {
DrawController drawController = DrawController();
void someFunction(){
drawController.clearCanvas();
notifyListeners();
}
}
The above method doesn't work. It does execute the function and changes the value but it does not change the UI (does not rebuild the widget).
Method 2:
(Controller A):
class DrawController extends ChangeNotifier {
void clearCanvas() {
selectedText = '';
shouldClearCanvas = true;
currentDrawPoints.initialPoint = null;
currentDrawPoints.endPoint = null;
print(currentDrawPoints.initialPoint);
notifyListeners();
}
}
Calling it from (Controller B):
class GameController extends ChangeNotifier {
DrawController drawController = DrawController();
void someFunction(BuildContext context){
Provider.of<DrawController>(context, listen: false).clearCanvas();
}
}
This second method works and it updates the UI too but I think this is the wrong method of doing it.
As using it after async await gives the warning "Do not use BuildContexts across async gaps."
class GameController extends ChangeNotifier {
DrawController drawController = DrawController();
void someFunction(BuildContext context) async {
User? userData = await localStorage.getUserData();
Provider.of<DrawController>(context, listen: false)
.clearCanvas(); //Gives warning "Do not use BuildContexts across async gaps."
}
}
Can anyone tell me the correct way of doing it.

Flutter GetX state management can't update the variables state

I Can't Change the state of any variable with .obs, RxType and Rx();
And when i try to change it it will give errors that ican't assign a value type int to variable type RxInt.
Here is the code:
class StateController extends GetxController {
late String sunrise;
late String sunset;
late int temperatureDegree;
late int maxDegree;
late int minDegree;
late double windSpeed;
late int humidity;
void updateUI(dynamic weatherDataInput) {
sunrise = weatherDataInput["current"]["sunrise"];
sunset = weatherDataInput["current"]["sunset"];
temperatureDegree = weatherDataInput["current"]["temp"];
maxDegree = weatherDataInput["daily"][0]["temp"]["max"];
minDegree = weatherDataInput["daily"][0]["temp"]["min"];
windSpeed = weatherDataInput["current"]["wind_speed"];
humidity = weatherDataInput["current"]["humidity"];
}
}
try this way,
class NameController extends GetxController{
final sunrise = ''.obs;
void updateSomeText(){
sunrise('Text updated'); //or sunrise(weatherDataInput["current"]
//["sunrise"].toString());
}
}
then to update it try wrap it with Obx e.g.:
final controller = Get.put(NameController());
Obx(
()=> Text(controller.sunrise.value)
),
You can use the update() method in the end of the updateUI() like this:
void updateUI(dynamic weatherDataInput) {
sunrise = weatherDataInput["current"]["sunrise"];
sunset = weatherDataInput["current"]["sunset"];
temperatureDegree = weatherDataInput["current"]["temp"];
maxDegree = weatherDataInput["daily"][0]["temp"]["max"];
minDegree = weatherDataInput["daily"][0]["temp"]["min"];
windSpeed = weatherDataInput["current"]["wind_speed"];
humidity = weatherDataInput["current"]["humidity"];
update();
}
,
and then use GetBuilder in your UI, alternatively, you should declare your variables as Rx, for example:
RxString sunrise = "".obs;
RxString sunset = "".obs;
and use observer widget in your UI:
Obx(
()=> Text(controller.sunset.value)
)
This will update your UI automatically when observables (sunrise and sunset) change.
.

Flutter Riverpod StateNotifierProvider: only watch changes for part of a model

I have a model like this:
class TimerModel {
const TimerModel(this.timeLeft, this.buttonState);
final String timeLeft;
final ButtonState buttonState;
}
enum ButtonState {
initial,
started,
paused,
finished,
}
And here is the StateNotifierProvider:
class TimerNotifier extends StateNotifier<TimerModel> {
TimerNotifier() : super(_initialState);
static const int _initialDuration = 10;
static final _initialState = TimerModel(
_durationString(_initialDuration),
ButtonState.initial,
);
final Ticker _ticker = Ticker();
StreamSubscription<int> _tickerSubscription;
void start() {
if (state.buttonState == ButtonState.paused) {
_tickerSubscription?.resume();
state = TimerModel(state.timeLeft, ButtonState.started);
} else {
_tickerSubscription?.cancel();
_tickerSubscription =
_ticker.tick(ticks: _initialDuration).listen((duration) {
state = TimerModel(_durationString(duration), ButtonState.started);
});
_tickerSubscription.onDone(() {
state = TimerModel(state.timeLeft, ButtonState.finished);
});
state =
TimerModel(_durationString(_initialDuration), ButtonState.started);
}
}
static String _durationString(int duration) {
final String minutesStr =
((duration / 60) % 60).floor().toString().padLeft(2, '0');
final String secondsStr =
(duration % 60).floor().toString().padLeft(2, '0');
return '$minutesStr:$secondsStr';
}
void pause() {
_tickerSubscription?.pause();
state = TimerModel(state.timeLeft, ButtonState.paused);
}
void reset() {
_tickerSubscription?.cancel();
state = _initialState;
}
#override
void dispose() {
_tickerSubscription?.cancel();
super.dispose();
}
}
class Ticker {
Stream<int> tick({int ticks}) {
return Stream.periodic(Duration(seconds: 1), (x) => ticks - x - 1)
.take(ticks);
}
}
I can listen for all changes in state like this:
final timerProvider = StateNotifierProvider<TimerNotifier>((ref) => TimerNotifier());
However I want to make another provider that only listens for changes in the ButtonState. This doesn't work:
final buttonProvider = StateProvider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
because it still returns all the state changes.
This also doesn't work:
final buttonProvider = StateProvider<ButtonState>((ref) {
return ref.watch(timerProvider.state.buttonState);
});
Because the state object doesn't have a buttonState property.
How do I only watch buttonState changes?
Using watch gives a new state whenever the watched state changes. So can solve the problem in two parts like so:
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
Using this provider will cause a rebuild every time the timerProvider.state changes. However, the trick is to do the following:
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
Since _buttonState will be the same for most of the timerProvider.state changes, watching _buttonState will only cause rebuilds when _buttonState actually changes.
Thanks to this post for showing the answer. That post also indicates that there will be a simplified syntax soon:
final buttonState = ref.watch(timerProvider.state.select((state) => state.buttonState));