Flutter Provider - Call function on value change without calling build() - flutter

I am wondering if it is possible to watch for a value change inside an initState which then simply calls a function?
I basically need to start and stop a timer given the status of a class i am observing with Provider (listen: true) and was hoping there was some callback functionality that i could trigger instead of build() being called each time?
For example something like..
void initState() {
super.initState();
Provider.of<MyService>(context, listen: true).serviceRunning => {
//do stuff (start/stop my local timer)
if(serviceRunning) {
serviceRunning()
} else {
serviceStopped()
}
}
}
void serviceRunning() {
//start local timer and other bits
}
void serviceStopped() {
//stop local timer and other bits
}
Widget build(BuildContext context) {
..
}
I don't recall Provider being able to do this, so would appreciate any suggestions on how to achieve this. As mentioned above, i am just trying to save having build() get called unnecessarily.

Not a provider user here (bloc fan) but you should be able to do what you want with a ValueNotifier exposed by your service. And in your initState you would add a listener to the notifier.
And I believe you can avoid build call by using context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.
Extract of the provider doc:
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
But I'm not sure that this is necessary.
If you find a cleaner solution please share it, it's always nice learning what works in other part of the flutter community.

Related

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

using didUpdateWidget to replace a stream causes "bad state: Stream has already been listened to"

I have a StreamBuilder that I want to be able to pause and unpause and, since StreamBuilder doesn't expose it's underlying StreamSubscription, I have to create a proxy stream to forward the source stream's elements while exposing a StreamSubscription that I can pause and unpause. This forces me to convert my stateless widget into a stateful widget so I have access to dispose and can close it's StreamController.
I'm following the didUpdateWidget documentation's instructions:
In initState, subscribe to the object.
In didUpdateWidget unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object.
In dispose, unsubscribe from the object.
I need to replace the Stream whenever the user types in a new search query. My stateful widget is re-constructed within a ChangeNotifier whenever a new stream is received.
Here's the relevant part of my stateful widget:
final Stream<List<Entry>> _entryStream;
StreamController<List<Entry>> _entryStreamController;
StreamSubscription<List<Entry>> _entryStreamSubscription;
_EntryListState(this._entryStream);
void initStream() {
_entryStreamController?.close();
_entryStreamController = StreamController();
_entryStreamSubscription = _entryStream.listen((event) {
_entryStreamController.add(event);
}
)..onDone(() => _entryStreamController.close());
}
#override
void initState() {
initStream();
super.initState();
}
#override
void dispose() {
_entryStreamController?.close();
super.dispose();
}
#override
void didUpdateWidget(EntryList oldEntryList) {
// Initialize the stream only if it has changed
if (oldEntryList._entryStream != _entryStream) initStream();
super.didUpdateWidget(oldEntryList);
}
This is the line throwing the BadState error:
_entryStreamSubscription = _entryStream.listen((event) {
_entryStreamController.add(event);
}
And here is where my widget is being constructed:
child: Consumer<SearchStringModel>(
builder: (context, searchStringModel, child) {
print('rebuilding with: ${searchStringModel.searchString}');
var entryStream = _getEntries(searchStringModel.searchString);
return EntryList(entryStream);
}
),
I don't think I understand didUpdateWidget in particular and suspect that's where the two issues (bad state, and not updating) are coming from? I'm also re-constructed the stateful widget instead of just modifying its state which is a little confusing, but the widget is intended to act as a stateless widget for all intents and purposes so it'd be a pain to update it to update state instead of reconstructing. Any advice?
I had not read the "didUpdateWidget" documentation closely enough. This is the key paragraph:
/// If the parent widget rebuilds and request that this location in the tree
/// update to display a new widget with the same [runtimeType] and
/// [Widget.key], the framework will update the [widget] property of this
/// [State] object to refer to the new widget and then call this method
/// with the previous widget as an argument.
In my consumer, I'm rebuilding with a new EntryList. As the above paragraph describes, the framework will then call didUpdateWidget on the same state object (as opposed to a new one). In order to check whether or not the configuration has changed and, if it has, to access the new configuration, I need to access the widget property of my state object. With this in mind, here is my new implementation of didUpdateWidget:
#override
void didUpdateWidget(EntryList oldEntryList) {
super.didUpdateWidget(oldEntryList);
// Initialize the stream only if it has changed
if (widget._entryStream != _entryStream) {
_entryStream = widget._entryStream;
initStream();
}
}
Note that I am now checking widet._entryStream and then setting the state's _entryStream to it if it's changed.
Hopefully someone else running into a similar problem will be helped by this!

Safe usage for useScrollController? (Flutter Hooks)

Would the following code be considered safe?
class SomeWidget extends HookWidget {
#override
Widget build(BuildContext context) {
final controller = useScrollController();
controller.addListener(_someCallback);
return ...;
}
}
I'm specifically referring to the addListener. In this ResoCoder hooks tutorial he adds the listener inside the initHook function of a custom hook.
I know that ResoCoder wrote the custom hook to dispose of the scrollController...I'm more curious as to how the controller listener behaves (I have no idea what is allowed and not allowed for listeners). Any resources on where I can learn about them would be great.
Thanks :)
Side-effects such as adding listeners should not be done directly inside build. If the widget rebuilt, that would cause the listener to be added again
Instead, you can use useEffect:
final controller = useScrollController();
useEffect(() {
controller.addListener(_someCallback);
return () => controller.removeListener(_someCallback);
}, [controller]);

Flutter, using a bloc in a bloc

I have two BLoCs.
EstateBloc
EstateTryBloc
My Application basically gets estates from an API and displays them in a similar fashion
Now I wanted to add a sort functionality, but I could only access the List of Estates via a specific state.
if(currentState is PostLoadedState){{
print(currentState.estates);
}
I wanted to make the List of estates available for whichever bloc, that needed that list.
What I did was, I created the EstateTryBloc, which basically contains the List of estates as a state.
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
#override
List<Estate> get initialState => [];
#override
Stream<List<Estate>> mapEventToState(
EstateTryEvent event,
) async* {
final currentState = state;
if(event is AddToEstateList){
final estates = await FetchFromEitherSource(currentState.length, 20)
.getDataFromEitherSource();
yield currentState + estates;
}
}
}
As I print the state inside the bloc I get the List of estates but I dont know how I would use that List in a different bloc.
print(EstateTryBloc().state);
simply shows the initialState.
I am open for every kind of answer, feel free to tell me if a different approach would be better.
When you do print(EstateTryBloc().state); you are creating a new instance of EstateTryBloc() that's why you always see the initialState instead of the current state.
For that to work, you must access the reference for the instance that you want to get the states of. Something like:
final EstateTryBloc bloc = EstateTryBloc();
// Use the bloc wherever you want
print(bloc.state);
Right now the recommended way to share data between blocs is to inject one bloc into another and listen for state changes. So in your case it would be something like this:
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
final StreamSubscription _subscription;
EstateTryBloc(EstateBloc estateBloc) {
_subscription = estateBloc.listen((PostState state) {
if (state is PostLoadedState) {
print(state.estates);
}
});
}
#override
Future<Function> close() {
_subscription.cancel();
return super.close();
}
}
To be honest I overcomplicated things a little bit and did not recognize the real problem.
It was that I accidently created a new instance of EstateBloc() whenever I pressed on the sort button.
Anyways, thanks for your contribution guys!

How do I know that the transition animation is finish when push a new page

When push a new page, I load the data in initState. This resulted in stuttering during the transition animation. This problem is particularly serious on Android.
In react native, I can use
InteractionManager.runAfterInteractions (() => {
// ... do some time-consuming operations ...
});
Is there a similar method in flutter. Let me know that the transition animation is finish?
In your destination page you can get an instance of ModalRoute.
ModalRoute has an animation property exposing addStatusListener to react to animation lifecycle events.
The following exemple may help you :
Widget build(BuildContext context) {
var route = ModalRoute.of(context);
// Defining an internal function to be able to remove the listener
void handler(status) {
if (status == AnimationStatus.completed) {
print('Animation completed !');
route.animation.removeStatusListener(handler);
}
}
route.animation.addStatusListener(handler);
..