Access Provider resources from outside Flutter - flutter

I'm developing the beginning of a card game where players must draw the first N cards and start playing.
My idea is to use a Deck that will handle all these processes giving/removing one or more Cards from owner's hand.
But, since can be a maximum of 6 players, how can it do it?
I was thinking to create one Row on the screen for each player and put here the List<Card> handled by Provider and Consumer.
With this kind of setup what the Deck must do is just to add one or more Cards to the line and it's all over. The problem is that I don't know how Deck can access to the List<Card> inside Provider's Consumer.
I was thinking that Deck should create 6 global keys, passing them to each Consumer so it can access all 6 states but I don't know how to do it, this is what I wrote.
Hand class, handles Cards :
class Hand extends ChangeNotifier {
final List<Card> _hand = [];
void addCard(Card card) {
_hand.add(card);
notifyListeners();
}
void removeAll() {
_hand.clear();
notifyListeners();
}
List<Card> getCards() {
return this._hand;
}
}
GameLineData just uses Provider and Consumer for Hand :
class GameLineData extends StatefulWidget {
Player player;
GameLineData({Key? key, required this.player}) : super(key: key);
#override
State<GameLineData> createState() => _GameLineDataState();
}
class _GameLineDataState extends State<GameLineData> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Hand(),
child: Consumer<Hand>(
builder: (context, value, child) {
return Row(
children: value.getCards(),
);
},
),
);
}
}
And Deck :
class Deck {
final Map<int, Key> keys = {
1:GlobalKey(),
2:GlobalKey(),
3:GlobalKey(),
4:GlobalKey(),
5:GlobalKey(),
6:GlobalKey(),
};
void giveCards() {
//TODO
}
}
Is my design correct? Any suggestion?

Related

is Flutter bloc always must have one property(data)

I am worker for a project on flutter with bloc as state management.
But my screen contain a wide variety of data.
How can I management all this data?
class ProductCubit extends Cubit<ProductState> {
Worker worker = Worker();
List<ProductMakePriceChange> productsPriceChange = [];
List<PurchaseCount> purchaseCount = [];
int productCount = 0;
int productSaleCount = 0;
int productCategoryCount = 0;
int productUnitCount = 0;
}
I have One state for each data (Loading state)
And One method for each data to load it
The problem!
when one state are change, all screen are rebuild
I need to change just one partition from my screen, just that partition when that data are effect
There are several ways to achieve this.
As you guessed correctly, you can of course move some fields to separate cubits. Another option would be to implement different subclasses of ProductState and check for the type in the BlocBuilder or BlocConsumer at runtime.
class ProductCubit extends Cubit<ProductState> {
ProductCubit()
: super(
const ProductInitial(
ProductInfo(
productsPriceChange: [],
purchaseCount: [],
productSaleCount: 0,
productCategoryCount: 0,
productCount: 0,
productUnitCount: 0,
),
),
);
Future<void> loadProductPurchaseCount() async {
emit(ProductPurchaseCountLoadInProgress(state.productInfo));
try {
// TODO load product purchase count
final productPurchaseCount = <dynamic>[];
emit(
ProductPurchaseCountLoadSuccess(
state.productInfo.copyWith(
purchaseCount: productPurchaseCount,
),
),
);
} catch (_) {
emit(
ProductPurchaseCountLoadSuccess(state.productInfo),
);
}
}
}
abstract class ProductState extends Equatable {
const ProductState(this.productInfo);
final ProductInfo productInfo;
#override
List<Object?> get props => [productInfo];
}
class ProductInitial extends ProductState {
const ProductInitial(ProductInfo productInfo) : super(productInfo);
}
class ProductPurchaseCountLoadInProgress extends ProductState {
const ProductPurchaseCountLoadInProgress(ProductInfo productInfo)
: super(productInfo);
}
class ProductPurchaseCountLoadFailure extends ProductState {
const ProductPurchaseCountLoadFailure(ProductInfo productInfo)
: super(productInfo);
}
class ProductPurchaseCountLoadSuccess extends ProductState {
const ProductPurchaseCountLoadSuccess(ProductInfo productInfo)
: super(productInfo);
}
Last, but not least, there is a relatively new Widget called BlocSelector which lets you check the state in order to determine whether the child should be built.
BlocSelector<BlocA, BlocAState, SelectedState>(
selector: (state) {
// return selected state based on the provided state.
},
builder: (context, state) {
// return widget here based on the selected state.
},
)
Check out the docs: https://pub.dev/packages/flutter_bloc

Delete an item in the source list from a flutter ListView

I'm starting to learn Flutter and I'm trying to write an application.
The application has a list of players in a ListView of SwitchListTile. This is working at the moment. I'm trying to add a function to delete one of the players from the lists.
class PlayersSwitchListTilesContainer extends StatelessWidget {
PlayersSwitchListTilesContainer({this.players});
final List<PlayerModel> players;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children : players.map<Widget>((PlayerModel p){
return PlayerSwtichListTile(singlePlayer: p);
}).toList()
),
);
}
}
class PlayerSwtichListTile extends StatefulWidget {
PlayerSwtichListTile({this.singlePlayer});
final PlayerModel singlePlayer;
void removePlayer()
{
// What goes here ???
print('Delete ' + singlePlayer.playerName);
}
#override
_PlayerSwtichListTileState createState() => new _PlayerSwtichListTileState(player: singlePlayer);
}
At the moment, when I try to delete a player it calls the correct code and prints the player's name. However, I'm struggling to see how to delete the player from the players list.
I'd be grateful for any pointers anyone has
From what I understand, to delete a player from the list of players you can do this but for this method, you need to provide the index number manually:
setState(() {
players.removeAt(index);
});
A better approach would be to use a listView.builder and add a button to the PlayerSwtichListTile which can receive the index from listView.builder so that whenever you click that button then that PlayerSwtichListTile player would get removed:
ListView.builder(
itemCount: players.length ?? 0,
itemBuilder: (context, index) {
return PlayerSwtichListTile(singlePlayer: p, index: index);}
)
class PlayerSwtichListTile extends StatefulWidget {
PlayerSwtichListTile({this.singlePlayer, this.index});
final int index;
final PlayerModel singlePlayer;
#override
_PlayerSwtichListTileState createState() => new _PlayerSwtichListTileState(player: singlePlayer);
}
class _PlayerSwtichListTile extends State<PlayerSwtichListTile > {
var player;
_PlayerSwtichListTile({this.player});
//call this function in ontap of that delete button
void removePlayer()
{
setState(() {
players.removeAt(widget.index);
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}

Recreate map in bloc pattern every time receive new location

I am trying to use bloc pattern in map application. Application when is started, user location is found first and then change the map center to user location.
When user moved i want to change marker on the map.This is my code:
class HomeFoursquareScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<LocationBloc, LocationState>(
builder: (context, locationState) {
if (locationState is LocationLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (locationState is LocationLoadedState) {
return _MyMap(locationState.location);
} else {
return const Text('oops...something went wrong');
}
},
);
}
}
class _MyMap extends StatelessWidget {
const _MyMap({Key key, #required this.location}) : super(key: key);
final Location location;
#override
Widget build(BuildContext context) {
return BlocBuilder<FourSquareBloc, FourSquareState>(
builder: (context, foursquareState) {
return FlutterMap(...);
}
);
},
)
But as you can see in my code every time my location is changed, that's mean when user is moving LocationLoadedState is triggered and _MyMap widget is called and FlutterMap is recreated !! I think it is not good for performance and it is not logical to create new instance of map continuously ! Is it right ? And What is right way?
I want to be fixed map but other thing like MapOptions or Marker get data when new data arrived.

How to handle navigation using stream from inheritedWidget?

I'm using an inherited Widget to access a Bloc with some long running task (e.g. search).
I want to trigger the search on page 1 and continue to the next page when this is finished. Therefore I'm listening on a stream and wait for the result to happen and then navigate to the result page.
Now, due to using an inherited widget to access the Bloc I can't access the bloc with context.inheritFromWidgetOfExactType() during initState() and the exception as I read it, recommends doing this in didChangeDependencies().
Doing so this results in some weird behavior as the more often I go back and forth, the more often the stream I access fires which would lead to the second page beeing pushed multiple times. And this increases with each back and forth interaction. I don't understand why the stream why this is happening. Any insights here are welcome. As a workaround I keep a local variable _onSecondPage holding the state to avoid pushing several times to the second Page.
I found now How to call a method from InheritedWidget only once? which helps in my case and I could access the inherited widget through context.ancestorInheritedElementForWidgetOfExactType() and just listen to the stream and navigate to the second page directly from initState().
Then the stream behaves as I would expect, but the question is, does this have any other side effects, so I should rather get it working through listening on the stream in didChangeDependencides() ?
Code examples
My FirstPage widget listening in the didChangeDependencies() on the stream. Working, but I think I miss something. The more often i navigate from first to 2nd page, the second page would be pushed multiple times on the navigation stack if not keeping a local _onSecondPage variable.
#override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("counter: $_counter -Did change dependencies called");
// This works the first time, after that going back and forth to the second screen is opened several times
BlocProvider.of(context).bloc.finished.stream.listen((bool isFinished) {
_handleRouting(isFinished);
});
}
void _handleRouting(bool isFinished) async {
if (isFinished && !_onSecondPage) {
_onSecondPage = true;
debugPrint("counter: $_counter - finished: $isFinished : ${DateTime.now().toIso8601String()} => NAVIGATE TO OTHER PAGE");
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
_onSecondPage = false;
} else {
debugPrint("counter: $_counter - finished: $isFinished : ${DateTime.now().toIso8601String()} => not finished, nothing to do now");
}
}
#override
void dispose() {
debugPrint("counter: $_counter - disposing my homepage State");
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder(
stream: BlocProvider.of(context).bloc.counter.stream,
initialData: 0,
builder: (context, snapshot) {
_counter = snapshot.data;
return Text(
"${snapshot.data}",
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
A simple Bloc faking some long running work
///Long Work Bloc
class LongWorkBloc {
final BehaviorSubject<bool> startLongWork = BehaviorSubject<bool>();
final BehaviorSubject<bool> finished = BehaviorSubject<bool>();
int _counter = 0;
final BehaviorSubject<int> counter = BehaviorSubject<int>();
LongWorkBloc() {
startLongWork.stream.listen((bool start) {
if (start) {
debugPrint("Start long running work");
Future.delayed(Duration(seconds: 1), () => {}).then((Map<dynamic, dynamic> reslut) {
_counter++;
counter.sink.add(_counter);
finished.sink.add(true);
finished.sink.add(false);
});
}
});
}
dispose() {
startLongWork?.close();
finished?.close();
counter?.close();
}
}
Better working code
If I however remove the code to access the inherited widget from didChangeDependencies() and listen to the stream in the initState() it seems to be working properly.
Here I get hold of the inherited widget holding the stream through context.ancestorInheritedElementForWidgetOfExactType()
Is this ok to do so? Or what would be a flutter best practice in this case?
#override
void initState() {
super.initState();
//this works, but I don't know if this is good practice or has any side effects?
BlocProvider p = context.ancestorInheritedElementForWidgetOfExactType(BlocProvider)?.widget;
if (p != null) {
p.bloc.finished.stream.listen((bool isFinished) {
_handleRouting(isFinished);
});
}
}
Personally, I have not found any reason not to listen to BLoC state streams in initState. As long as you remember to cancel your subscription on dispose
If your BlocProvider is making proper use of InheritedWidget you should not have a problem getting your value inside of initState.
like So
void initState() {
super.initState();
_counterBloc = BlocProvider.of(context);
_subscription = _counterBloc.stateStream.listen((state) {
if (state.total > 20) {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return TestPush();
}));
}
});
}
Here is an example of a nice BlocProvider that should work in any case
import 'package:flutter/widgets.dart';
import 'bloc_base.dart';
class BlocProvider<T extends BlocBase> extends StatefulWidget {
final T bloc;
final Widget child;
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}) : super(key: key);
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<_BlocProviderInherited<T>>();
_BlocProviderInherited<T> provider =
context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
return provider?.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<BlocBase>> {
#override
Widget build(BuildContext context) {
return _BlocProviderInherited<T>(
bloc: widget.bloc,
child: widget.child,
);
}
#override
void dispose() {
widget.bloc?.dispose();
super.dispose();
}
}
class _BlocProviderInherited<T> extends InheritedWidget {
final T bloc;
_BlocProviderInherited({
Key key,
#required Widget child,
#required this.bloc,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
... and finally the BLoC
import 'dart:async';
import 'bloc_base.dart';
abstract class CounterEventBase {
final int amount;
CounterEventBase({this.amount = 1});
}
class CounterIncrementEvent extends CounterEventBase {
CounterIncrementEvent({amount = 1}) : super(amount: amount);
}
class CounterDecrementEvent extends CounterEventBase {
CounterDecrementEvent({amount = 1}) : super(amount: amount);
}
class CounterState {
final int total;
CounterState(this.total);
}
class CounterBloc extends BlocBase {
CounterState _state = CounterState(0);
// Input Streams/Sinks
final _eventInController = StreamController<CounterEventBase>();
Sink<CounterEventBase> get events => _eventInController;
Stream<CounterEventBase> get _eventStream => _eventInController.stream;
// Output Streams/Sinks
final _stateOutController = StreamController<CounterState>.broadcast();
Sink<CounterState> get _states => _stateOutController;
Stream<CounterState> get stateStream => _stateOutController.stream;
// Subscriptions
final List<StreamSubscription> _subscriptions = [];
CounterBloc() {
_subscriptions.add(_eventStream.listen(_handleEvent));
}
_handleEvent(CounterEventBase event) async {
if (event is CounterIncrementEvent) {
_state = (CounterState(_state.total + event.amount));
} else if (event is CounterDecrementEvent) {
_state = (CounterState(_state.total - event.amount));
}
_states.add(_state);
}
#override
void dispose() {
_eventInController.close();
_stateOutController.close();
_subscriptions.forEach((StreamSubscription sub) => sub.cancel());
}
}

Controlling State from outside of a StatefulWidget

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.