Describe what scenario you think is uncovered by the existing examples/articles
For example, how to provide ThemeData got by Theme.of(context)?
This code works on 0.6.0-dev, but it throws error on 0.6.1.
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() => runApp(const ProviderScope(child: App()));
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return Consumer(
(context, watch) {
watch(themeProvider).state = Theme.of(context);
return child;
},
);
},
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: const Text('Dog'),
onPressed: () {
context.read(_controller).foo();
},
),
),
);
}
}
final _controller = Provider((ref) => _Controller(ref.read));
class _Controller {
_Controller(this._read);
final Reader _read;
void foo() {
print('brightness: ${_read(themeProvider).state.brightness}');
}
}
final themeProvider = StateProvider<ThemeData>((ref) => null);
Error:
E/flutter (15851): [ERROR:flutter/lib/ui/ui_dart_state.cc(171)] Unhandled Exception: setState() or markNeedsBuild() called during build.
E/flutter (15851): This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
E/flutter (15851): The widget on which setState() or markNeedsBuild() was called was:
E/flutter (15851): UncontrolledProviderScope
E/flutter (15851): The widget which was currently being built when the offending call was made was:
E/flutter (15851): Consumer
E/flutter (15851): #0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4229:11)
E/flutter (15851): #1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4244:6)
E/flutter (15851): #2 ProviderElement._debugMarkWillChange.<anonymous closure> (package:riverpod/src/framework/base_provider.dart:691:16)
E/flutter (15851): #3 ProviderElement._debugMarkWillChange (package:riverpod/src/framework/base_provider.dart:695:6)
I feels the error makes sense, but I'd like to know how to handle this situation.
Provider can handle this situation very naturally by using Provider(value:).
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Provider(
create: (context) => _Controller(context.read),
child: const HomePage(),
),
builder: (context, child) {
return Provider.value(
value: Theme.of(context),
child: child,
);
},
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: const Text('Dog'),
onPressed: () {
context.read<_Controller>().foo();
},
),
),
);
}
}
class _Controller {
_Controller(this._read);
Locator _read;
void foo() {
print('brightness: ${_read<ThemeData>().brightness}');
}
}
Describe why existing examples/articles do not cover this case
I've checked these, but I couldn't find the solution.
This error was added on purpose, as modifying providers during the build step is a quite dangerous idea. Not only that, but the fact that the ThemeData value varies depending on where you are in the widget tree makes this complex.
If you don't plan on using ref.watch to listen to theme change, you could use a simple Provider:
class ThemeContainer {
ThemeData data;
}
final themeProvider = Provider((ref) => ThemeContainer());
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return Consumer(
(context, watch) {
watch(themeProvider).data = Theme.of(context);
return child;
},
);
},
);
}
}
If you don't plan on using ref.watch to listen to theme change, you could use a simple Provider:
Thanks, yes my usecase is to read by using Reader, so it is enough 馃憤
@rrousselGit Not that I have the use case for "watching" a value depending on BuildContext, but I was thinking if this would work or if it could cause any problem.
final themeProvider = StateProvider<ThemeData>((ref) => null);
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return HookBuilder(
(context) {
final themeData = Theme.of(context);
useEffect((){
WidgetsBinding.instance.addPostFrameCallback((_) {
themeProvider.read(context).state = themeData;
});
}, [themeData]);
return child;
},
);
},
);
}
}
That should be fine yes.
Most helpful comment
That should be fine yes.