Reputation: 2109
I'm using flutter_bloc and have created a sessionState that gets triggered when a user has successfully authenticated and the state saves the user object (did
).
I use a Cubit to change the state which then results in showing different screens in the ui. But whenever I want to access the did
object of the Verified sessionState which holds the user information, I have to create an if else statement in every file to check if the sessionState is Verified
and if that is true I can access the did object with state.did
.
I would be interested to know if it's possible to provide this did
object to all underlying widgets without passing it down manually every widget.
I would like to be able to just access the did object from the context so that I can access it everywhere below where it is provided.
My SessionState:
abstract class SessionState {}
class UnkownSessionState extends SessionState {}
class Unverified extends SessionState {}
class Verified extends SessionState {
Verified({required this.did});
final Did did;
}
The SessionCubit
launches states that I use to define the global state of the app and show different screens as a result.
class SessionCubit extends Cubit<SessionState> {
SessionCubit(this.commonBackendRepo) : super(UnkownSessionState()) {
attemptGettingDid();
}
final CommonBackendRepo commonBackendRepo;
final SecureStorage secureStorage = SecureStorage();
Future<void> attemptGettingDid() async {
try {
//more logic
emit(Unverified());
} catch (e) {
emit(Unverified());
}
}
void showUnverified() => emit(Unverified());
void showSession(Did did) {
emit(Verified(did: did));
}
}
This is how I currently access the did
object in every file:
BlocBuilder<SessionCubit, SessionState>(builder: (context, state) {
if (state is Verified) {
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
elevation: 0.0,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(
state.did.username,
style: Theme.of(context).textTheme.headline5,
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: CredentialDetailsView(),
))
],
);
} else {
return const Text("Unverified");
}
});
Edit:
That's how I would imagine the optimal solution to my scenario:
did
object.Response to Robert Sandberg's answer
I also have an AppNavigator
that either start the authNavigator
or sessionNavigator
. So the sessionNavigator
is the parent widget of all widgets that get shown only when the state is Verified
. So I think this would be a great place to wrap the sessionNavigator with the Provider.value
, as you explained it.
I wrapped my SessionNavigator with the Provider.value
widget and provided the state object:
class AppNavigator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SessionCubit, SessionState>(
builder: (context, state) {
return Navigator(
pages: [
if (state is UnkownSessionState)
MaterialPage(child: StartupScreen()),
//show auth flow
if (state is Unverified)
MaterialPage(
child: BlocProvider(
create: (context) => AuthCubit(context.read<SessionCubit()),
child: AuthNavigator(),
)),
//show session flow
if (state is Verified)
MaterialPage(
child: Provider.value(
// here I can access the state.did object
value: state,
child: SessionNavigator(),
))
],
onPopPage: (route, result) => route.didPop(result),
);
},
);
}
}
For testing I tried to watch the provided value inside of my Home screen which is a child of the SessionNavigator:
...
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
//
final sessionState = context.watch<SessionState>();
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: 60.0,
backgroundColor: Colors.white,
flexibleSpace: FlexibleSpaceBar(
title: Text(
// I can't access state.did because value is of type SessionState
sessionState.did,
style: Theme.of(context).textTheme.headline6,
),
titlePadding:
const EdgeInsetsDirectional.only(start: 20, bottom: 16)),
)
],
);
}
}
But because the type of my value is SessionState
I can't access the did object of the underlying Verified state. I though about providing the state.did object but I don't know how I would watch for that type.
I also got the error that my home screen couldn't find the Provider in the build context. Could that happen because my Home screen has a Navigator
as parent?
This is a visualization of my app architecture:
Upvotes: 0
Views: 2205
Reputation: 8645
I use this pattern sometimes, and it sounds like my solution very much fits your imagined optimal solution :)
In a parent:
BlocBuilder<MyBloc, MyBlocState>(
builder: (context, state) {
return state.when(
stateA: () => SomeWidgetA(),
stateB: () => SomeWidgetB(),
stateC: (myValue) {
return Provider.value(
value: myValue,
child: SomeWidgetC(),
);
}),
}
)
... and then in SomeWidgetC()
and all it's children I do the following to get hold of myValue
(where it is of type MyValueType
)
final myValue = context.watch<MyValueType>();
If you want, you could also use regular InheritedWidget instead of using the Provider package but you get a lot of nifty features with Provider.
EDIT Response to your further questions.
In your case, if you provide the entire state as you do in AppNavigator
, then you should look for Verified
state in SessionNavigator as such:
final sessionState = context.watch<Verified>();
Or, if you only need the Did object, then just provide that, so in AppNavigator:
if (state is Verified)
MaterialPage(
child: Provider.value(
value: state.did,
child: SessionNavigator(),
))
And then in SessionNavigator:
final did = context.watch<Did>();
Edit 3: It might be that your problems arises with nested Navigators. Have a look at this SO answer for some guidance.
If you continue struggle, I suggest that you open new SO questions for the specific problems, as this is starting to get a bit wide :) I think that the original question is answered, even though you haven't reached a final solution.
Upvotes: 3
Reputation: 5353
You can use something like (state as Verified).did.username
, but I would still recommend using the if/else pattern since you ensure that you are using the correct state.
In my personal projects, I use the Freezed package (https://pub.dev/packages/freezed) for such cases since there are some useful methods to cover this. For instance, by using freezed, your issue could be resolved as:
return state.maybeWhen(
verified: (did) => CustomScrollView(...),
orElse: () => const Text("Unverified"),
)
Upvotes: 0