Article on context drilling #39
Replies: 3 comments 8 replies
-
I read the article, thanks for mentioning solidart as a state management solution 💙 Context drillingclass UserCtx {
final user = createSignal<User?>(null);
final isPremium = createSignal<bool>(false);
}
class ChildCtx {
final UserCtx userCtx;
final anotherState = createSignal();
}
// Assuming we have a bloc at the height of `ChildCtx` that only needs to know if the user is premium.
class SomeBloc {
SomeBloc(this.childCtx);
final ChildCtx childCtx;
void someFn() {
if (childCtx.userCtx.isPremium()) {
// do something
}
}
}
What I propose with solidart is simply: class UserBloc {
final user = createSignal<User?>(null);
final isPremium = createSignal<bool>(false);
}
class ParentWidget extends StatelessWidget {
const ParentWidget({super.key});
@override
Widget build(BuildContext context) {
return Solid(
providers: [
SolidProvider(create: () => UserBloc()),
],
child: const ChildWidget(),
);
}
}
class SomeBloc {
SomeBloc({required this.isPremium});
final Signal<bool> isPremium;
}
class ChildWidget extends StatelessWidget {
const ChildWidget({super.key});
@override
Widget build(BuildContext context) {
return Solid(
providers: [
SolidProvider(
create: () => SomeBloc(
isPremium: context.get<UserBloc>().isPremium,
),
),
],
child: const Placeholder(),
);
}
} As you can see the dependency has been made explicit correctly and |
Beta Was this translation helpful? Give feedback.
-
Hey, sorry if I reply only now. I just ported a side project from Riverpod to Solidart and wanted to test your library well before answering. With context drilling, dependencies are not injected. That is right. The context objects are supposed to be passed down explicitly. This way, type safety is guaranteed. The fact that When the Solid widget is preferableThere are cases where context drilling is not ideal. For example, it is much more convenient to use the class _ProvidersWrappingAppRouter extends StatelessWidget {
const _ProvidersWrappingAppRouter();
@override
Widget build(BuildContext context) => Solid(
signals: const {SignalIds.designLanguage: createDesignLanguageSignal},
child: Solid(
signals: const {SignalIds.themeMode: createThemeModeSignal},
child: Builder(
builder: (context) {
// ... and Signal<ThemeMode> createThemeModeSignal() {
const key = 'theme_mode'; // shared preferences key
final themeModeSignal = createSignal<ThemeMode>(
() {
final themeModeString = sharedPreferences.getString(key);
if (themeModeString == null) {
logger.v('No $key key found in shared preferences');
sharedPreferences.setString(key, ThemeMode.system.name);
return ThemeMode.system;
} else {
logger
..v('$key key found in shared preferences with value: $themeModeString')
..v('Valid ThemeMode strings: ${ThemeMode.values.map((ui) => ui.name)}');
if (ThemeMode.values.map((ui) => ui.name).contains(themeModeString)) {
// The value inside shared preferences is valid.
final loadedUi =
ThemeMode.values.singleWhere((ui) => ui.name == themeModeString);
return loadedUi;
} else {
// The value inside shared preferences is not valid.
const resetThemeMode = ThemeMode.system;
sharedPreferences.setString(key, resetThemeMode.name);
return resetThemeMode;
}
}
}(),
);
createEffect((dispose) {
final themeMode = themeModeSignal();
sharedPreferences.setString(key, themeMode.name);
});
return themeModeSignal;
}
Now, wherever you need the value of the ThemeMode signal, you can use a When context drilling is preferableYou might find yourself creating some signals of that are not on top of the widget tree. E.g., calling (NB: of course, the One of the advantages of context drilling is that if you change something in one of the context object, the IDE is gonna detect automatically if something broke or not. On the contrary, if you change one signal inserted in a |
Beta Was this translation helpful? Give feedback.
-
Would love to see a sample project using context drilling to understand pratically how it works. I know that getting Solid signals throws at runtime, but I think this is acceptable and the extension GetSignalExt on BuildContext {
Signal<T> getSignal<T>(Object id) {
return get<Signal<T>>(id);
}
ReadSignal<T> getReadSignal<T>(Object id) {
return get<ReadSignal<T>>(id);
}
} And simply use I know this isn't really type-safe, in fact in my projects I tend to use Instead I use Context drilling seems to me to be an additional layer on top of the providers. |
Beta Was this translation helpful? Give feedback.
-
I am excited to share context drilling, a state-management pattern I have developed the past couple months. It is thought for large-scale codebases and it is compatible with this library.
I also wrote a companion article called State management patterns and their potential drawbacks.
Would love to get your insights. Questions are also welcome!
If you like this pattern, maybe we could mention it somewhere in the docs.
Beta Was this translation helpful? Give feedback.
All reactions