BuildContext in a Flutter BLoC class - flutter

In short, does the context belong in the BLoC class and if it doesn't, what's the right approach?
I'm using a Provider as an abstraction layer between the Firebase DB and the UI. Recently, we've been abstracting further away to use the BLoC pattern, so that the widgets don't manipulate the data in the Provider directly. It's all proceeding nicely, but due to the fact that we use both the providers and the BLoC, I am not sure how to use the BuildContext properly, as the context ha more to do with the Widgets/UI than the business logic.
Here's an example:
class SomeWidget extends StatelessWidget {
final SomeWidgetBloc bloc;
SomeWidget({Key key, this.bloc});
#override
Widget build(BuildContext context) => StreamBuilder(stream: bloc.getSomeData,
builder: (context, snapshot) {
return Text(snapshot.data ?? "Empty");
}
}
class SomeWidgetBloc {
BuildContext context; // should it be here? Currently, it's needed for the Provider
SomeWidgetBloc(BuildContext context);
Stream<String> get getSomeData {
return Provider.of<SomeFirebaseProvider>(context).fetchSomeData();
}
}

You should pass your SomeFirebaseProvider instance to the SomeWidgetBloc constructor at creation time.

Related

Is it better practice to have nested consumer of providers (not talking about multi providers) or have one provider

Suppose I have a change notifier class
class State extends ChangeNotifier{
}
This change notifier change class should be able to trigger update (notify) two widgets where one widget is child of the other.
I am wondering which of these two patterns is better approach when designing flutters apps.
Have nested consumers of change notifier objects. Is it good practice to have a consumer that build a widget that have as a child another widget that itself also have its own consumer for same class.
e.g pseudocode for simplicity
class Widget1State extends State<Widget1> {
Widget build(BuildContext context) {
Consumer<State>(
builder: (context, state, _) {
child: Widget2()
}
}
}
class Widget2State extends State<Widget2> {
Widget build(BuildContext context) {
Consumer<State>( // nested consumer here Widget1->Widget2
builder: (context, state, _) {
child: ...
}
}
}
Have just one consumer of change notifier at the root and propagate class state as variable?
class Widget1State extends State<Widget1> {
Widget build(BuildContext context) {
Consumer<State>(
builder: (context, state, _) {
child: Widget2(state /*now i am passing the change notifier as variable*/)
}
}
}
class Widget2 extends StatefulWidget {
State state;
}
class Widget2State extends State<Widget2> {
Widget build(BuildContext context) {
child: ...widget.state... // just use widget.state, no need for consumer
}
}
If the better choice is option-2, should i use stateless widget instead?
Thanks a lot for help and sorry if this is stupid question or confusing, i am new to flutter.

How can I use flutter provider to get data from Firestore?

I used here Future Provider to get data from firestore But it's not allowing me to set the initial Data to null??? It ask me to input a type of . How can I use future Provider to get data from firestore.
class MyHomePage extends StatelessWidget {
final _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
return FutureProvider<DocumentSnapshot>(create: (_)async{
return FirebaseFirestore.instance.collection("User").doc("xxxx").get();}, initialData: ,child: Welcome,)
}}
Widget Welcome (BuildContext context){
final document = Provider.of<DocumentSnapshot>(context).data;
if(document==null){
return Container(
child: Text("Loading"),);}
}
Instead of creating a FutureProvider of DocumentSnaphot, a good solution would be to create a class that wraps the DocumentSnapshot. For example:
class MyClass {
MyClass(){}
Future<DocumentSnapshot> getData() async {
return await FirebaseFirestore.instance.collection("User").doc("xxxx").get();
}
}
And in the provider declaration you might set something like
...
Provider(create: (_) => MyClass())
...
This wouldn't require you to set the initial data.
However, for your case and what it seems that you are trying to do, using an StreamProvider would be better.
For more examples and details on this, I recommend checking out the following websites. You'll find more useful information there.
https://firebase.flutter.dev/docs/firestore/usage
https://pub.dev/documentation/cloud_firestore/latest/

How to make an object from a bloc available for all other bloc in Flutter

I am using Bloc for my Flutter project. I have created three blocs. These are AuthenticationBloc, FirebaseDatabaseBloc, and ChatMessagesBloc. When the user gets authenticated, AuthenticationBloc emits a state called authenticated with a user object.
I want to make this user object available inside FirebaseDatabaseBloc and ChatMessagesBloc. What is the clean way of doing this?
Well, This is year 2022 and a lot has changed. Bloc to Bloc to communication via the constructor is now considered a bad practice. Nobody said it won't work though but trust me, you'd end up tightly coupling your code.
Generally, sibling dependencies between two entities in the same architectural layer should be avoided at all costs, as it creates tight-coupling which is hard to maintain. Since blocs reside in the business logic architectural layer, no bloc should know about any other bloc.
documentation.
You should rather try this:
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<WeatherCubit, WeatherState>(
listener: (context, state) {
// When the first bloc's state changes, this will be called.
//
// Now we can add an event to the second bloc without it having
// to know about the first bloc.
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
},
child: TextButton(
child: const Text('Hello'),
onPressed: () {
BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
},
),
);
}
}
I hope it helps!
This is achievable by BLoC-to-BLoC communication. The simplest way is to pass your BLoC reference by the other's constructor and subscribe to BLoC changes:
#override
Widget build(BuildContext context) {
final authenticationBloc = AuthenticationBloc();
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>.value(value: authenticationBloc),
BlocProvider<FirebaseDatabaseBloc>(
create: (_) => FirebaseDatabaseBloc(
authenticationBloc: authenticationBloc,
),
),
],
child: ...,
);
}
Then, inside the FirebaseDatabaseBloc you can subscribe to changes:
class FirebaseDatabaseBloc extends Bloc<FirebaseDatabaseEvent, FirebaseDatabaseBloc> {
final AuthenticationBloc authenticationBloc;
StreamSubscription<AuthenticationState> _authenticationStateStreamSubscription;
FirebaseDatabaseBloc({
#required this.authenticationBloc,
}) : super(...) {
_authenticationStateStreamSubscription = authenticationBloc.listen(_onAuthenticationBlocStateChange);
}
#override
Future<void> close() async {
_authenticationStateStreamSubscription.cancel();
return super.close();
}
void _onAuthenticationBlocStateChange(AuthenticationState authState) {
// Do whatever you want with the auth state
}
}
For more info, you can check this video: https://www.youtube.com/watch?v=ricBLKHeubM

How to have access Context in didChangeAppLifecycleState lifecycle hook using flutter HookWidget?

I'm trying to access context so i can read my provider but since this lifecycle hook is out side the widget tree. it's not accessible. is there a way to get access to context?
I researched a little bit and finally discussed with narcodico from the flutter bloc community, so the credits are for him.
Therefore, mixin WidgetsBindingObserver on a state class, the context is available even in the overrides like didChangeAppLifecycleState since they are part of the state class.
Also, take in consideration to move to BlocProvider above the state widget.
Example
class HomePageProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<InAppPurchasesBloc>(),
child: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
...
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
context
.read<InAppPurchasesBloc>()
.add(const InAppPurchasesEvent.getPurchaserInfo());
}
}
...
}
I am afraid you can't access context inside didChangeAppLifecycleState.
For anyone interested, you can save your scaffold state in a global key, and access the context from its current state.
You can use useEffect function, read more:
https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useEffect.html;
Widget build(BuildContext context) {
useEffect(() {
//what would you write in initState
},
);
You may consider using the Riverpod package instead of Provider. Riverpod is from the same author as Provider and considered the "better Provider", but with many improvements including Flutter independence, meaning it does not rely on a context to work, and you can use it almost the same way as provider.
Using Riverpod, along with Flutter Hooks, you can do something like:
// create a provider in a global context
final myProvider = Provider((ref) => myClass());
// access the provider inside your class
class MyWidget extends HookWidget{
//access the provider using a hook
final myClassProvider = useProvider(myProvider);
//... your logic
#override
Widget build (BuildContext context){/* ... build widget tree... */}
}
Consider this very useful and concise tutorial with how to use Riverpod with Flutter Hooks and StateNotifier, ChangeNotifier, etc...

ProviderNotFoundException was thrown

This is the code I'm using
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return LoginPage();
} else {
return MyHomePage();
}
}
}
and the user file has
class User {
final String uid;
User({this.uid});
}
And I am getting this error:
The following ProviderNotFoundException was thrown building Wrapper(dirty):
Error: Could not find the correct Provider above this Wrapper Widget
This likely happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that Wrapper is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
You need to provide the User object somewhere in a widget above Wrapper
Something like this:
Provider(
create: (_) => User(),
child: Wrapper()
)
https://pub.dev/documentation/provider/latest/