I have a StateNotifier whose state I want to use in a widget.
As far as I know, if you watch a StateNotifierProvider with ref.watch in a build, the widget gets rebuilt every time the state changes.
Now, In the StateNotifier I have a DatabaseService instance to call api requests and set the state accordingly and in a ConsumerWidget I watch the state.
The thing is, I want to call a fetch method defined in StateNotifier the first time the widget builds, so I can display the data retrieved from the database.
Something like this
class MyStateNotifier extends StateNotifier<CustomState> {
final DatabaseService databaseService;
MyStateNotifier(this.databaseService) : super(CustomStateInit());
Future<void> fetchData() async {
state = CustomStateLoading();
final result = await databaseService.apiRequest();
state = CustomStateSuccess(result);
}
}
final stateNotifierProvider = StateNotifierProvider((ref) => MyStateNotifier());
and in the widget
class MyWidget extends ConsumerWidget {
// I have to call the MyStateNotifier fetchData() method to get the data
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(stateNotifierProvider);
return Column(
children: data.map((e) => Text(e)).toList()
);
}
}
To call fetchData() once you watch your stateNotifierProvider you need to call it in the StateNotifier's constructor like this:
MyStateNotifier(this.databaseService) : super(CustomStateInit()){fetchData();}
Related
I am at the begin of my Cubit learning and i tried to create a "Liter-Tracker" with sharedPrefs. Everything works but not the init state. I have to press a Button first because I initialize the drinkValue with 0. I tried to return an Int with the value from shared prefs but this dont work :( May you help me?
This is my cubit:
class DrinkCubit extends Cubit<DrinkState> {
DrinkCubit() : super(DrinkState(drinkValue: 0));
Future<void> loadCounter() async {
final prefs = await SharedPreferences.getInstance();
state.drinkValue = (prefs.getInt('counter') ?? 0);
}
Future<int> loadInitCounter() async {
final prefs = await SharedPreferences.getInstance();
return state.drinkValue = (prefs.getInt('counter') ?? 0);
}
}
and this my cubit state:
class DrinkState {
int drinkValue;
int? amount;
DrinkState({
required this.drinkValue,
});
}
I also tried something like this in my MainPage, how i usually init my state with setState:
#override
void initState() {
super.initState();
BlocProvider.of<DrinkCubit>(context).loadCounter();
}
Context is not accessible in initstate, try using didChangeDependencies life cycle method Flutter get context in initState method
Firstly, I strongly advise you to avoid the StatefulWidget when you use BLoC, but it doesn't mean you can't use it at all. Just be careful because setState() can rebuild BlocProvider inside the stateful widget.
As for the initialization process, I suggest you use this approach on the BlocProvider.
class DrinkScreen extends StatelessWidget {
const DrinkScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DrinkCubit()..loadCounter(), // Or call other initialization method
child: DrinkView(),
);
}
}
This approach works really well if you reuse this screen multiple times, for example, you redirect to DrinkScreen every time you want to fill data and you dispose of the screen afterward (Navigate.pop(), etc). This way you can automatically initialize the cubit every time you redirect into this screen, you don't need to use StatefulWidget to init the cubit.
In our system we used Pusher on both of our Admin Panel and mobile application, in flutter i want to wrap whole application screens with Pusher and Riverpod and using providers with Hook widget in screens, that means i want to implement Pusher in main or MyApp and broadcast received data from Pusher on all of screens which we have them, basically we had to use Pusher in each screen which we need to receive data and its difficult to maintain application, for example:
this sample code is not correct and i tried to implement that, but i can't
here as you can see Pusher events and listeners work fine without any problem:
D/PusherClientPlugin( 7447): Event stream cancelled.
D/PusherClientPlugin( 7447): Event stream listening...
D/PusherClientPlugin( 7447): Event stream cancelled.
D/PusherClientPlugin( 7447): Event stream listening...
D/PusherClientPlugin( 7447): [BIND] new-login
D/PusherClientPlugin( 7447): Event stream cancelled.
D/PusherClientPlugin( 7447): Event stream listening...
D/PusherClientPlugin( 7447): Event stream cancelled.
D/PusherClientPlugin( 7447): Event stream listening...
D/PusherClientPlugin( 7447): [BIND] new-login
D/PusherClientPlugin( 7447): [ON_EVENT] Channel: backers-list, EventName: new-login,
D/PusherClientPlugin( 7447): Data: Sending Test Data , User Id: null
I/flutter ( 7447): received
SpashScreen class as initializing Pusher
class SplashScreen extends HookWidget{
#override
Widget build(BuildContext context) {
_routeNavigation();
final PusherClient pusher = PusherClient(
"xxxx",
PusherOptions(
cluster: 'us2',
));
final Channel channel;
pusher.connect();
channel = pusher.subscribe("backers-list");
channel.bind("new-login", (event) {
print('received');
context
.read(alarmNotificationStateProvider.notifier)
.increment('${event?.data.toString()}');
});
return Scaffold(
/* ... */
Profile screen should be receive data from Pusher which i implemented that into SplashScreen
class Profile extends HookWidget {
#override
Widget build(BuildContext context) {
final alarmNotification = useProvider(alarmNotificationStateProvider);
print('PUSH RECEIVED');
/* ... */
}
alarmNotificationStateProvider :
final alarmNotificationStateProvider = StateNotifierProvider<AlarmNotification, AlarmNotificationData>(
(ref) => AlarmNotification(),
);
AlarmNotification class:
class AlarmNotification extends StateNotifier<AlarmNotificationData> {
AlarmNotification() : super(_initialValue);
static const _initialValue = AlarmNotificationData('');
void increment(String data) {
state = AlarmNotificationData(data);
}
}
AlarmNotificationData class:
class AlarmNotificationData {
final String data;
const AlarmNotificationData(this.data);
}
To listen changes in riverpod you require useProvider(provider);
The useProvider method will listen for state changes in provider, which is the current instance of your model class
This also gives you a reference to the notifier state, which you’re storing in model class
use this:
final alarmNotificationNotifier = useProvider(alarmNotificationProvider);
instead of final alarmNotificationNotifier = context.read(alarmNotificationProvider);
the read method gets you a reference to your state management class (CounterNotifier) without listening for changes in the state
I haven't seen your code of AlarmNotification, so I use a simple version below.
class AlarmNotification extends ChangeNotifier{
var _data;
String get data => _data;
void receivedData(String data){
_data = data;
notifyListeners();
}
}
I think you misunderstand a little about riverpod. The MyApp seems only provide the event data but not consume it. So the part you can just use StatelessWidget.(It is also suggested to use "context.read(someProvider)" if you only read not watch)
final alarmNotificationProvider = ChangeNotifierProvider((ref)=>AlarmNotification());
class MyApp extends StatelessWidget{
const MyApp({Key? key}):super(key: key);
#override
Widget build(BuildContext context) {
...
channel.bind("new-login", (event) {
context
.read(alarmNotificationProvider)
.receivedData('${event?.data.toString()}');
});
...
}
}
And the other widgets that need the data just listen to the alarmNotificationProvider (you may need to import the file to see it). It will rebuild the widget once it gets the notification.
class OtherWidget extends HookWidget {
const OtherWidget({Key? key}):super(key: key);
#override
Widget build(BuildContext context) {
final alarmNotification = useProvider(alarmNotificationProvider);
return Text(alarmNotification.data);
}
}
We are also using pusher. We are using flutter_pusher package and notifications work as expected. you can initialize it by
e.g.
Pusher.init('API_KEY', PusherOptions(encrypted:true));
Try it this way,
/// Code
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<SettingsProvider>(
create: (context) => SettingsProvider(),
),
],
child: MyApp(),
),
);
///
class SettingsProvider extends ChangeNotifier {
Locale _currentLocale = Locale("en", "");
String _languageCode = "en";
Locale get getCurrentLocale => _currentLocale;
String get getLanguageCode => _languageCode;
void updateLocale(Locale locale) {
Log.loga(title, "updateLocale: >>>> $locale");
Log.loga(title, "updateLocale: toLanguageTag >>>> ${locale.toLanguageTag()}");
this._currentLocale = locale;
AppTranslations.load(locale);
this._languageCode = locale.toLanguageTag();
//notifyListeners();
Future.delayed(Duration(seconds: 0)).then((value) {
super.notifyListeners();
});
}
}
/// update data
Provider.of<SettingsProvider>(context, listen: false).updateLocale(locale);
/// get data
Provider.of<SettingsProvider>(context).getCurrentLocale
make sure both screeens are under the changenotifier in widget tree,
other option is pass your provider to login screen as argument and use changenotifier.value() in login screen,
I have a provider like so:
final profileImagesProvider =
ChangeNotifierProvider.family<ProfileImagesNotifier, List<String>>(
(ref, profileImages) => ProfileImagesNotifier(profileImages));
class ProfileImagesNotifier extends ChangeNotifier {
ProfileImagesNotifier(List<String> images);
List<String> images;
}
However, when I try to use the provider:
Widget build(BuildContext context, ScopedReader watch) {
var profileImages = watch(ProfileImagesNotifier(['test_1.png', 'test_2.png']))
print(profileImages.images) //null
}
The list is retrieved as a null.
Am I doing this right? (I'm a completely noob when it comes to river pod and state management).
Was incorrectly calling the constructor.
Should have been:
ProfileImagesNotifier(this.images);
rather than
ProfileImagesNotifier(List<String> images);
I am new to flutter and I am woking on sockets which keeps streaming data which I need to update on a appbar. So I have to stateful widgets
class _HomePageState extends State<HomePage> {
TickerAppBar appBar = TickerAppBar();
//Some declarations
#override
void initState() {
// TODO: implement initState
super.initState();
connectionSetup();
}
connectionSetup() async {
this.socket.connect(connectionString);
this.socket.sendSubscription(subscriptionString);
await processData();
}
Future<Null> processData() async{
subscription = socket.streamController.stream.listen(
(data) {
appBar.setStreamData(data); //Passing data into appbar widget
//rest of the code of current widget
}
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
//rest of the code
)
}
}
class TickerAppBar extends StatefulWidget implements PreferredSizeWidget {
String streamData;
_TickerAppBarState child = _TickerAppBarState();
setStreamData(data){
this.streamData = data;
child.processData(); //To invoke the function every time the data is passed
}
}
class _TickerAppBarState extends State<TickerAppBar>{
Future<String> processData() async{
//Cannot set state here since its a constructor and the widget is not mounted.
}
}
So how I can set the data every time the data is passed into tickerappbar? so that my custom appbar displays new data which comes in every second.
I would suggest you follow the BLoC pattern, in which you can define your streams at one place and subscribe to the said stream from any widget, once you have the data ready, you can write to the stream and it will be shared on all the subscribed widgets.
I'm trying to understand the best practice for controlling a StatefulWidget's state outside of that Widgets State.
I have the following interface defined.
abstract class StartupView {
Stream<String> get onAppSelected;
set showActivity(bool activity);
set message(String message);
}
I would like to create a StatefulWidget StartupPage that implements this interface. I expect the Widget to do the following:
When a button is pressed it would send an event over the onAppSelected stream. A controller would listen to this event and perform some action ( DB call, service request, etc ).
The controller can call showActivity or set message to have the view show progress with a message.
Because a Stateful Widget does not expose its State as a property, I don't know the best approach for accessing and modifying the State's attributes.
The way I would expect to use this would be something like this:
Widget createStartupPage() {
var page = new StartupPage();
page.onAppSelected.listen((app) {
page.showActivity = true;
//Do some work
page.showActivity = false;
});
}
I've thought about instantiating the Widget by passing in the state I want it to return in createState() but that feels wrong.
Some background on why we have this approach: We currently have a Dart web application. For view-controller separation, testability, and forward-thinking towards Flutter, we decided that we would create an interface for every view in our application. This would allow a WebComponent or a Flutter Widget to implement this interface and leave all of the controller logic the same.
There are multiple ways to interact with other stateful widgets.
1. findAncestorStateOfType
The first and most straightforward is through context.findAncestorStateOfType method.
Usually wrapped in a static method of the Stateful subclass like this :
class MyState extends StatefulWidget {
static of(BuildContext context, {bool root = false}) => root
? context.findRootAncestorStateOfType<_MyStateState>()
: context.findAncestorStateOfType<_MyStateState>();
#override
_MyStateState createState() => _MyStateState();
}
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return Container();
}
}
This is how Navigator works for example.
Pro:
Easiest solution
Con:
Tempted to access State properties or manually call setState
Requires to expose State subclass
Don't use this method when you want to access a variable. As your widget may not reload when that variable change.
2. Listenable, Stream and/or InheritedWidget
Sometimes instead of a method, you may want to access some properties. The thing is, you most likely want your widgets to update whenever that value changes over time.
In this situation, dart offer Stream and Sink. And flutter adds on the top of it InheritedWidget and Listenable such as ValueNotifier. They all do relatively the same thing: subscribing to a value change event when coupled with a StreamBuilder/context.dependOnInheritedWidgetOfExactType/AnimatedBuilder.
This is the go-to solution when you want your State to expose some properties. I won't cover all the possibilities but here's a small example using InheritedWidget :
First, we have an InheritedWidget that expose a count :
class Count extends InheritedWidget {
static of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<Count>();
final int count;
Count({Key key, #required Widget child, #required this.count})
: assert(count != null),
super(key: key, child: child);
#override
bool updateShouldNotify(Count oldWidget) {
return this.count != oldWidget.count;
}
}
Then we have our State that instantiate this InheritedWidget
class _MyStateState extends State<MyState> {
int count = 0;
#override
Widget build(BuildContext context) {
return Count(
count: count,
child: Scaffold(
body: CountBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
count++;
});
},
),
),
);
}
}
Finally, we have our CountBody that fetch this exposed count
class CountBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(Count.of(context).count.toString()),
);
}
}
Pros:
More performant than findAncestorStateOfType
Stream alternative is dart only (works with web) and is strongly integrated in the language (keywords such as await for or async*)
Automic reload of the children when the value change
Cons:
More boilerplate
Stream can be complicated
3. Notifications
Instead of directly calling methods on State, you can send a Notification from your widget. And make State subscribe to these notifications.
An example of Notification would be :
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
To dispatch the notification simply call dispatch(context) on your notification instance and it will bubble up.
MyNotification(title: "Foo")..dispatch(context)
Note: you need put above line of code inside a class, otherwise no context, can NOT call notification.
Any given widget can listen to notifications dispatched by their children using NotificationListener<T> :
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: onTitlePush,
child: Container(),
);
}
bool onTitlePush(MyNotification notification) {
print("New item ${notification.title}");
// true meaning processed, no following notification bubbling.
return true;
}
}
An example would be Scrollable, which can dispatch ScrollNotification including start/end/overscroll. Then used by Scrollbar to know scroll information without having access to ScrollController
Pros:
Cool reactive API. We don't directly do stuff on State. It's State that subscribes to events triggered by its children
More than one widget can subscribe to that same notification
Prevents children from accessing unwanted State properties
Cons:
May not fit your use-case
Requires more boilerplate
You can expose the state's widget with a static method, a few of the flutter examples do it this way and I've started using it as well:
class StartupPage extends StatefulWidget {
static StartupPageState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<StartupPageState>());
#override
StartupPageState createState() => new StartupPageState();
}
class StartupPageState extends State<StartupPage> {
...
}
You can then access the state by calling StartupPage.of(context).doSomething();.
The caveat here is that you need to have a BuildContext with that page somewhere in its tree.
There is another common used approach to have access to State's properties/methods:
class StartupPage extends StatefulWidget {
StartupPage({Key key}) : super(key: key);
#override
StartupPageState createState() => StartupPageState();
}
// Make class public!
class StartupPageState extends State<StartupPage> {
int someStateProperty;
void someStateMethod() {}
}
// Somewhere where inside class where `StartupPage` will be used
final startupPageKey = GlobalKey<StartupPageState>();
// Somewhere where the `StartupPage` will be opened
final startupPage = StartupPage(key: startupPageKey);
Navigator.push(context, MaterialPageRoute(builder: (_) => startupPage);
// Somewhere where you need have access to state
startupPageKey.currentState.someStateProperty = 1;
startupPageKey.currentState.someStateMethod();
I do:
class StartupPage extends StatefulWidget {
StartupPageState state;
#override
StartupPageState createState() {
this.state = new StartupPageState();
return this.state;
}
}
class DetectedAnimationState extends State<DetectedAnimation> {
And outside just startupPage.state
While trying to solve a similar problem, I discovered that ancestorStateOfType() and TypeMatcher have been deprecated. Instead, one has to use findAncestorStateOfType(). However as per the documentation, "calling this method is relatively expensive". The documentation for the findAncestorStateOfType() method can be found here.
In any case, to use findAncestorStateOfType(), the following can be implemented (this is a modification of the correct answer using the findAncestorStateOfType() method):
class StartupPage extends StatefulWidget {
static _StartupPageState of(BuildContext context) => context.findAncestorStateOfType<_StartupPageState>();
#override
_StartupPageState createState() => new _StartupPageState();
}
class _StartupPageState extends State<StartupPage> {
...
}
The state can be accessed in the same way as described in the correct answer (using StartupPage.of(context).yourFunction()). I wanted to update the post with the new method.
You can use eventify
This library provide mechanism to register for event notifications with emitter
or publisher and get notified in the event of an event.
You can do something like:
// Import the library
import 'package:eventify/eventify.dart';
final EventEmitter emitter = new EventEmitter();
var controlNumber = 50;
List<Widget> buttonsGenerator() {
final List<Widget> buttons = new List<Widget>();
for (var i = 0; i < controlNumber; i++) {
widgets.add(new MaterialButton(
// Generate 10 Buttons afterwards
onPressed: () {
controlNumber = 10;
emitter.emit("updateButtonsList", null, "");
},
);
}
}
class AState extends State<ofYourWidget> {
#override
Widget build(BuildContext context) {
List<Widget> buttons_list = buttonsGenerator();
emitter.on('updateButtonsList', null, (event, event_context) {
setState(() {
buttons_list = buttonsGenerator();
});
});
}
...
}
I can't think of anything which can't be achieved by event driven programming. You are limitless!
"Freedom cannot be bestowed — it must be achieved."
- Elbert Hubbard
Have you considered lifting the state to the parent widget? It is a common, though less ideal than Redux, way to manage state in React as far as I know, and this repository shows how to apply the concept to a Flutter app.