Flutter firebase provider architecture - flutter

I have a stateful widget that has an animated container. Inside that animated container I have a streamProvider connected to firebase. My problem is that when I animate using setState the entire widget rebuilds and another call to firebase is made. My solution was to lift the streamProvider up and wrap the widget that's animated with that streambuilder. But that means I need to create another widget and hence more boilerplate.
I feel like what I'm doing is wrong but I'm kind of stuck because all provider resources are related to authentication...
Does anyone have any ideas how I can get around this in a clean way? and is setState the right way to trigger animations in a stateful widget?

For animating, try using AnimatedBuilder its the easiest way to animate, but I guess it won't fix your issue.
Personally I always use the Provider package, I don't know if you are doing it too.
So usually firebase provides you with a stream of data (if you are using it with cloud functions its different)
Now you could use a StreamBuilder with the Stream firebase provides you and use the data of the stream. With this version rebuilding the Widget won't lead to the app connecting to the server and fetching new data.
If you really like to use a ChangeNotifier you can use that stream inside the ChangeNotifier, listen to it and always notifying listeners of changes to occur with this implementation there won't be any unnecessary network calls either.
Some examples for the second version:
class SomeNotifier extends ChangeNotifier {
List<MyData> dataList = [];
SomeNotifier() {
Firestore.instance.collection("MyCollection").snapshots().listen((data) {
dataList = data.documents.map((doc) => MyData.fromDoc(doc));
notifyListeners();
});
}
}
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<SomeNotifier>(
create: (context) => SomeNotifier(),
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
var notifier = Provider.of<SomeNotifier>(context);
return Container(); //Here you can use your animated widget, it will be rebuilt to animate propperly
//It will also rebuild every time data in firebase changes
},
),
);
}
}
I hope this answers your question.

Related

Flutter clean architecture with Bloc, RxDart and StreamBuilder, Stateless vs Stateful and dispose

I'm trying to implement a clean architecture with no dependency of the framework in the business' logic layers.
The following example is a Screen with only a Text. I make an API Rest call in the repository and add the response to a BehaviorSubject that is listened through a StreamBuilder that will update the Text. Since is an StatefulWidget I'm using the dispose method to close the BehaviorSubject's StreamController.
The example is simplified, no error/loading state handling, no dependency injection, base classes, dispose interfaces etc.
class Bloc {
final UserReposiotry _userReposiotry;
final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);
Bloc(this._userReposiotry) {
_getActiveUsersCount();
}
void _getActiveUsersCount() async {
final response = await _userReposiotry.getActiveUsersCount();
_activeUsersCount.add(response.data);
}
ValueStream<int> get activeUsersCount => _activeUsersCount.stream;
void dispose() async {
await _activeUsersCount.drain(0);
_activeUsersCount.close();
}
}
class StatefulScreen extends StatefulWidget {
final Bloc bloc;
const StatefulScreen({Key? key, required this.bloc}) : super(key: key);
#override
State<StatefulScreen> createState() => _StatefulScreenState();
}
class _StatefulScreenState extends State<StatefulScreen> {
#override
Widget build(BuildContext context) {
final stream = widget.bloc.activeUsersCount;
return StreamBuilder<int>(
stream: stream,
initialData: stream.value,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
}
);
}
#override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
}
I have the following doubts regarding this approach.
StreamBuilder cancels the stream subscription automatically, but it doesn't close the StreamController. I know that you should close it if you are reading a file, but in this case, if I don't manually close it, once the StatefulScreen is no longer in the navigation stack, could it be destroyed, or it would be a memory leak?
I've seen a lot of people using StatelessWidget instead of StatefulWidget using Stream and StreamBuilder approach, if it is really needed to close the BehaviorSubject it is a problem since we don't have the dispose method, I found about the WillPopScope but it won't fire in all navigation cases and also and more important would it be more performant an approach like WillPopScope, or having an StatefulWidget wrapper (BlocProvider) inside an StatelessWidget just to do the dispose, than using an StatefulWidget directly, and if so could you point to an example of that implementation?
I'm currently choosing StatefulWidget for widgets that have animations o controllers (map, text input, pageview...) or streams that I need to close, the rest StatelessWidget, is this correct or am I missing something?
About the drain method, I'm using it because I've encountered an error navigating back while an API rest call was on progress, I found a member of the RxDart team saying it isn't really necessary to call drain so I'm confused about this too..., the error:
You cannot close the subject while items are being added from addStream
Thanks for your time.

Assign value of TextEditingController inside buildMethod

i have some questions about which is the correct way of creating TexEditingController;
Assuming that i want to create a controller with a fixed text, so i can do like this :
TextEditingController bioEditorController = new TextEditingController(text:"dummy");
Now my questions is this:
if i'm usign a stateful widget i can create this controller ad assign an initial text by doing this :
TextEditingController bioEditorController;
#override
void initState() {
bioEditorController = new TextEditingController(text: "dummy");
super.initState();
}
but if i'm not using a stateful widget is it correct to make something like this :
#override
Widget build(BuildContext context) {
TextEditingController bioEditorController =
new TextEditingController(text: controller.profile.value.bio);
What i mean is it correct to create this controller inside the build method, if i do like this it works , but i think that probably is not the best way of doing this things, also becauze i know that controller should also disposed....
I really need some help about clarifying this. Thanks
You are correct in assuming that doing that in the build method is not ideal. You have way less control over how many times the build method runs vs initState.
And while it's generally true that you would need a stateful widget when dealing with TextEditingControllers (or hooks), if you use GetX state management it's absolutely fine, and preferred to not bother with a stateful widget just because you need a TextEditingController.
Another benefit of this way is very easy access to the value of the TextEditingController from anywhere else in the app.
Here's a quick example. This is a class where you can keep all your TextEditingControllers.
class TextController extends GetxController {
RxString textfieldString = ''.obs; // observable String
TextEditingController textController;
// this onInit replaces initState of a stateful widget
#override
onInit() {
super.onInit();
textController = TextEditingController(text: 'dummy');
// adding a listener that automatically updates the textfieldString with the textfield value
textController.addListener(() {
textfieldString.value = textController.text;
debugPrint(textfieldString.value);
});
}
}
Initialize the controller in your main or anytime before you actually use it. This is when the onInit from that class is called.
Get.put(TextController()).textController;
Here's Page1 a stateless widget with an already initialized TextEditingController
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<TextController>(); // finding same initialized controller
return Scaffold(
body: Center(
child: TextFormField(
controller: controller.textController, // using TextEditingConroller from GetX class
),
),
);
}
}
And here's a quick example of a text widget on a different page automatically updating anytime the user types into the TextFormField
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller =
Get.find<TextController>(); // finding same instance of controller
return Scaffold(
body: Center(
// this Obx widget rebuilds based on any updates
child: Obx(
() => Text(controller.textfieldString.value),
),
),
);
}
}
So no matter where you are in your app, you can access the value of that TextFormField and you don't have to use a stateful widget. The GetxController will be removed from memory when not in use.
At this point the only time I ever need to use a stateful widget is when I need the AutomaticKeepAliveClientMixin.
Even animations can be done with stateless widgets in GetX by adding SingleGetTickerProviderMixin to a Getx class and doing everything there that would normally clutter up your stateful widget.

Why should didUpdateWidget be implemented for subscriptions and ChangeNotifiers?

I created a custom widget that listens to a ChangeNotifier and invokes a provided callback whenever the notifier fires. This is used for performing one-time tasks like navigation when the notifier changes.
Everything seems to work fine, but just by accident I stumbled upon the documentation of didUpdateWidget that states:
If a State's build method depends on an object that can itself change state, for example a ChangeNotifier or Stream, or some other object to which one can subscribe to receive notifications, then be sure to subscribe and unsubscribe properly in initState, didUpdateWidget, and dispose:
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'm handling the first and last point for obvious reasons, but could somebody shed a light on why I also have to implement didUpdateWidget? What could go wrong if I don't?
Bonus question: I'm not using provider in my application, yet. Does it offer something like this already out of the box? I couldn't find something like this.
My widget code:
class ChangeNotifierListener<T extends ChangeNotifier> extends StatefulWidget {
final Widget child;
final T changeNotifier;
final void Function(T changeNotifier) onChanged;
ChangeNotifierListener(
{#required this.child,
#required this.changeNotifier,
#required this.onChanged});
#override
_ChangeNotifierListenerState createState() =>
_ChangeNotifierListenerState<T>();
}
class _ChangeNotifierListenerState<T extends ChangeNotifier>
extends State<ChangeNotifierListener<T>> {
VoidCallback _callback;
#override
Widget build(BuildContext context) => widget.child;
#override
void initState() {
super.initState();
_callback = () {
widget.onChanged(widget.changeNotifier);
};
widget.changeNotifier.addListener(_callback);
}
#override
void dispose() {
widget.changeNotifier.removeListener(_callback);
super.dispose();
}
}
This part of the documentation is about how it is feasible that your widget rebuilds with a different parameters.
For example, with StreamBuilder, a first build may be similar to:
StreamBuilder(
stream: Stream.value(42),
builder: ...
)
And then something changes, and StreamBuilder is rebuilt with:
StreamBuilder(
stream: Stream.value(21),
builder: ...
)
In which case, stream changed. Therefore, StreamBuilder needs to stop listening to the previous Stream and listen to the new one.
This would be done though the following didUpdateWidget:
StreamSubscription<T> subscription;
#override
void didUpdateWidget(StreamBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.stream != oldWidget.stream) {
subscription?.cancel();
subscription = widget.stream?.listen(...);
}
}
The same logic applies to ChangeNotifier and any other observable object.

Make a Stream in StreamBuilder only run once

I have which cycles a heavy function X times. If I put this stream in a StreamBuilder, the Stream runs again and again forever, but I only need it to run once (do the X cycles) and the stop.
To solve this problem for future functions I used an AsyncMemoizer, but I cannot use it for stream functions.
How can I do it?
If you are sure, your widget should not be rebuilt, than try sth like this code below.
The _widget will be created once in initState, then the 'cached' widget will be returned in the build method.
class MyStreamWidget extends StatefulWidget {
#override
_MyStreamWidgetState createState() => _MyStreamWidgetState();
}
class _MyStreamWidgetState extends State<MyStreamWidget> {
StreamBuilder _widget;
// TODO your stream
var myStream;
#override
void initState() {
super.initState();
_widget = StreamBuilder(
stream: myStream,
builder: (context, snapshot) {
// TODO create widget
return Container();
})
}
#override
Widget build(BuildContext context) {
return _widget;
}
}
As RĂ©mi Rousselet suggested, the StreamBuilder should be used in a Widget Tree where the state is well managed. I was calling setState((){}) in the Stream which caused the UI to update every time, making the StreamBuilder rebuild so restarting the stream.

streambuilder is rebuilding again and again when keyboard popup or closes

Here I was stuck in a problem. I have a column of widgets with a stream builder and a text field. When i try to input some text, the keyboard pops up and then the stream builder rebuilds again or when the keyboard closes, the stream builder rebuilds again. As i am building a chat screen, I don't want to rebuild the stream builder again as it increases in number of reads.
Any sort of suggestions helpful.
Flutter calls the build() method every time it wants to change
anything in the view, and this happens surprisingly often.
You can pass the stream into the stateless widget
MyApp({Key key, this.stream}) : super(key: key);
Or build the stream in the initState method if the widget is statefull.
#override
void initState() {
super.initState();
post = buildStream();
}
What #TuanNguyen means by
build the stream in the initState method
is the following, if for you are using Firestore for exemple:
class MyStateFullWidget extends StatefulWidget {
const MyStateFullWidget({Key key}) : super(key: key);
#override
_MyStateFullWidgetState createState() => _MyStateFullWidgetState();
}
class _MyStateFullWidgetState extends State<MyStateFullWidget> {
Stream _myStream;
#override
void initState() {
super.initState();
_myStream = FirebaseFirestore.instance.collection(myCollection) ... .snapshots();
}
#override
Widget build(BuildContext context) {
return SomeUpperWidget(
child:
StreamBuilder(
stream: _myStream,
builder: (ctx, snap) => ... ,
)
);
}
}
I was facing the same issue. I could not find simple alternative to avoid re-rendering without changing much code. So I ended up like this:
So in the Bloc class, first initiated a variable say
streamStateIndex = 0;
And wherever I am using
sink.add(data)
, I started using
streamStateIndex++;
sink.add({"data": data, "streamStateIndex":streamStateIndex});
And initiated another variable lets say localStreamStateIndex = 0; inside Stateful flutter class to compare streamStateIndex from bloc
And used inside StreamBuilder like this:
if(snapshot.hasData){
if(localStreamStateIndex < snapshot.data['streamStateIndex']){
updateLocalState(snapshot.data['data']);
localStreamStateIndex = snapshot.data['streamStateIndex'];
}
}