Please Check the MUTATION section where I've commented what is the issue:
store.dart
import 'package:velocity_x/velocity_x.dart';
import '../model.dart';
class MyStore extends VxStore {
MyItem? itemA;
MyItem? itemB;
MyStore() {
itemA = MyItem(); // note: 'new' keyword is obsolated now.
itemB = MyItem();
}
}
///----------- MUTATION-------------------///
class PrintMutation extends VxMutation<MyStore> {
#override
void perform() {
print("...............Mutation Starts..................");
store!.itemA!.name = "phone"; // assume we wrote some data into itemA
store!.itemB = store!.itemA; // copying the data to itemB
print('Item B:${store!.itemB!.name!}'); // prints "phone" (as needed)
store!.itemB!.name = 'fruit'; // modifing the name
print('Item A:${store!.itemA!.name!}'); // Item A changes to Fruit, WHY??
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:velocity_x/velocity_x.dart';
import 'core/store.dart';
void main() {
runApp(VxState(child: MyApp(), store: MyStore()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => PrintMutation(),
child: "Print".text.make(),
),
),
);
}
}
pubspec.yaml
dependencies:
velocity_x: ^3.3.0
Related
The following code was working before null safety with flutter_bloc 4.0.1 but after null safety migration the state is not updating / emitting / broadcasting as expected with flutter_bloc 7.3.3.
The below _reactToState and mapEventToState methods are never called. How can I fix it?
Splash Screen
class SplashScreen extends StatefulWidget {
final Strapper strapper;
final Service? service;
SplashScreen(this.strapper, this.service);
#override
State<StatefulWidget> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc? _splashBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_splashBloc == null) {
_splashBloc = SplashBloc(widget.strapper, widget.service);
_splashBloc!.stream.listen(_reactToState);
}
}
#override
dispose() {
_splashBloc?.close();
_splashBloc = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider<SplashBloc>(
create: (context) => _splashBloc!,
child: BlocBuilder<SplashBloc, SplashBlocState>(
builder: (context, state) => Container(
child: Stack(
children: <Widget>[
LogoPanel(
_showWidgetForState(state),
),
],
),
),
),
);
}
void _reactToState(SplashBlocState state) {
if (state is InitializingSplashBlocState) {
if (widget.logOut) {
_splashBloc!.add(LogoutSplashBlocEvent());
} else {
_splashBloc!.add(CInitializationSplashBlocEvent());
}
} else if (state is AuthSuccessSplashBlocState) {
App.navigateToSomewhere(context, state.isNewUser);
}
}
Widget _showWidgetForState(SplashBlocState state) {
if (state is InitializingSplashBlocState) {
return _getProgressIndicator();
} else if (state is ChooseSomethingSplashBlockState ) {
return _showSignInWidget();
}
}
}
Splash Bloc
class SplashBloc extends Bloc<SplashBlocEvent, SplashBlocState> {
final Strapper? strapper;
final Service? service;
SplashBloc(this.strapper, this.service) : super(InitializingSplashBlocState());
#override
Stream<SplashBlocState> mapEventToState(event) async* {
if (event is CInitializationSplashBlocEvent) {
await strapper!.run();
}
bool chooseSomething = !service!.hasSomeSelection;
if (chooseSomething) {
yield ChooseSomethingSplashBlockState();
} else if (event is RAuthSplashBlocEvent) {
yield AuthSplashBlocState();
var authState = await _run();
yield authState;
}
}
Future<SplashBlocState> _run() async {
// Do something
}
}
Splash Bloc Event
abstract class SplashBlocEvent extends Equatable {
const SplashBlocEvent();
#override
List<Object> get props => [];
}
class CInitializationSplashBlocEvent extends SplashBlocEvent {}
class RAuthSplashBlocEvent extends SplashBlocEvent {}
Splash Bloc State
abstract class SplashBlocState extends Equatable {
const SplashBlocState();
#override
List<Object> get props => [];
}
class InitializingSplashBlocState extends SplashBlocState {}
class AuthSplashBlocState extends SplashBlocState {}
class ChooseSomethingSplashBlockState extends SplashBlocState {}
class AuthSuccessSplashBlocState extends SplashBlocState {
final CurrentUser? user;
final bool isNewUser;
AuthSuccessSplashBlocState(this.user, this.isNewUser);
}
As per the documentation:
In v6.0.0, the above snippet does not output the initial state and only outputs subsequent state changes. The previous behavior can be achieved with the following:
final bloc = MyBloc();
print(bloc.state);
bloc.listen(print);
So I changed my code in the Splash screen as following:
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_splashBloc == null) {
_splashBloc = SplashBloc(widget.strapper, widget.service);
_reactToState(_splashBloc!.state); // Added this line
_splashBloc!.stream.listen(_reactToState);
}
}
And that's it. It worked!
_reactToState and mapEventToState are definitely being called.
when you use Streamcontrollers it greatly simplifies state. I build a bloc code to manage state. The materialapp child is the splashWidget whose job is to render the hour, minute, second from bloc code emitting Time state. If the user clicks the splash screen or 5 seconds elapses the splash screen will be replaced with the HomePageWidget. bloc code controls the starting and stopping of the timer using an timerState event.
'package:flutter/material.dart';
import 'bloc_splash.dart';
import 'main.dart';
class SplashWidget extends StatelessWidget {
const SplashWidget({Key? key}) : super(key: key);
_redirectToHome(BuildContext context)
{
Navigator.pushReplacement(context,MaterialPageRoute(builder:(_)=>MyHomePage(title:"helloWorld")));
}
String _displayClock(Time ? data)
{
String retVal="";
if (data!=null)
{
retVal="Time: ${data.hour} : ${data.minute} : ${data.second}";
}
return retVal;
}
#override
Widget build(BuildContext context) {
SplashBloc _bloc=SplashBloc();
_bloc.timerOnChange(StartTimer());
return Scaffold(
body:InkWell(
onTap: (){_bloc.timerOnChange(StopTimer());
_redirectToHome(context);
},
child:Container(
child:
StreamBuilder<TimeState>(
stream:_bloc.timeStream,
builder:(context,snapshot)
{
if(snapshot.hasData && (snapshot.data is RedirectState))
{
return MyHomePage(title:"helloWorld");
}
return Center(child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Splash Screen", style:TextStyle(fontSize: 24,fontWeight: FontWeight.bold)),
Text(_displayClock(snapshot.data?.time)),
]));
}
)
))
);
}
}
bloc code
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:ui';
import 'dart:async';
abstract class TimerEvent extends Equatable{
const TimerEvent();
#override
List<Object>get props=>[];
}
class StartTimer extends TimerEvent{
const StartTimer();
}
class StopTimer extends TimerEvent{
const StopTimer();
}
class Time{
final int hour;
final int minute;
final int second;
Time(this.hour,this.minute,this.second);
}
class TimeState extends Equatable{
final Time time;
TimeState(this.time);
#override
List<Object> get props=>[time];
}
class RedirectState implements TimeState{
final Time time;
RedirectState(this.time);
#override
List<Object> get props=>[time];
#override
// TODO: implement stringify
bool? get stringify => throw UnimplementedError();
}
class TimerState extends Equatable{
final bool started;
const TimerState(this.started);
#override
List<Object> get props => [started];
}
class SplashBloc
{
SplashBloc();
Timer ?_timer;
var countDown=5;
Stream<TimeState> get timeStream=> _timeController.stream;
final _timeController =BehaviorSubject<TimeState>();
void dispose()
{
_timeController.close();
}
void _pushTimeOnTheStream(Timer timer)
{
DateTime now=DateTime.now();
_timeController.sink.add(TimeState(Time(now.hour,now.minute,now.second)));
this.countDown-=1;
if (this.countDown==0)
{
timerOnChange(StopTimer());
_timeController.sink.add(RedirectState(Time(0,0,0)));
}
}
void timerOnChange(TimerEvent event) {
if (event is StartTimer)
{
_timer=Timer.periodic(Duration(seconds: 1),_pushTimeOnTheStream);
}
else if(event is StopTimer){
//_timerController.sink.add(TimerState(false));
_timer?.cancel();
}
}
}
app
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const SplashWidget(),
);
}
}
Flutter riverpod is not notifying the Consumer on the state change when the StateNotifier's type is List, while the same implementation works just fine for other types.
here, I provided a minimal reproducable example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
class CounterState extends StateNotifier<List<int>> {
static final provider = StateProvider(
(ref) => CounterState(),
);
int get last {
print('last');
return state.last;
}
int get length {
print('len');
return state.length;
}
// the body of this will be provided below
add(int p) {}
CounterState() : super(<int>[0]);
}
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, watch) {
void _incrementCounter() {
final _count = Random.secure().nextInt(100);
context.read(CounterState.provider.notifier).state.add(_count);
}
var count = watch(CounterState.provider.notifier).state.length;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'You have pushed the button this many times: $count',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
as for the add method, I tried implementing it in a lot of ways, but neither works.
here is what I tried:
1: just add it straight away:
add(int p) {
state.add(p);
}
2: I also tried the solution suggested in this answer:
add(int p) {
state = [...state, p];
}
3: I tried to destroy the list entirely, and reassign it:
add(int p) {
final _state = [];
// copy [state] to [_state]
for (var item in state) {
_state.add(item);
}
// empty the state
state = [];
// add the new element
_state.add(p);
// refill [state] from [_state]
for (var item in _state) {
state.add(item);
}
print(state.length); // it continues until here and prints
}
Firstly, you are not creating the correct provider to listen to a StateNotifier. You need to change this:
static final provider = StateProvider(
(ref) => CounterState(),
);
to this:
static final provider = StateNotifierProvider<CounterState, List<int>>(
(ref) => CounterState(),
);
Please refer to the Riverpod documentation about the different types of providers.
Secondly, you are not actually watching for state changes, but you are just getting the state object from the notifier.
Change this line:
var count = watch(CounterState.provider.notifier).state.length;
to this:
final count = watch(CounterState.provider).length;
also, your increment method is not correct for StateNotifier providers. Please change this:
context.read(CounterState.provider.notifier).state.add(_count);
to this:
context.read(CounterState.provider.notifier).add(_count);
It should rebuild now when the state changes. However, you do need an implementation of your add method that actually changes the state object itself. I would suggest the second variant you mentioned, that is in my opinion the nicest way to do this:
add(int p) {
state = [...state, p];
}
#TmKVU explained well, so I'm skipping that part. You can also follow riverpod document.
here is my example of riverPod:
stateNotifierProvider
stateProvider
Your widget
import 'dart:math';
import 'package:stack_overflow/exports.dart';
class CounterState extends StateNotifier<List<int>> {
static final provider = StateNotifierProvider(
(ref) => CounterState(),
);
int get last {
print('last');
return state.last;
}
int get length {
print('len');
return state.length;
}
// the body of this will be provided below
add(int p) {}
CounterState() : super(<int>[0]);
}
class MyHomePageSSSS extends ConsumerWidget {
#override
Widget build(BuildContext context, watch) {
void _incrementCounter() {
final _count = Random.secure().nextInt(100);
context.read(CounterState.provider.notifier).state =
context.read(CounterState.provider.notifier).state..add(_count);
}
final countprovider = watch(CounterState.provider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'You have pushed the button this many times: ${countprovider.length}',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
I'm building an app which fetches hotel names from an API. I'm using the BLoC library. I managed to create whole service which downloads the data, but the result doesn't show in my terminal.
My BLoC works, it downloads the data. I saw it in Dart DevTools, but the state doesn't change and it does not show up.
Here's my code:
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/controllers/hotel/hotel_controller.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelLoading());
final HotelController hotelController = HotelController();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final Hotels hotels = await hotelController.parseHotels();
yield HotelFinal(hotels);
} catch (error) {
HotelError(error);
}
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotels hotels;
HotelFinal(this.hotels);
Hotels getHotel() {
return hotels;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {
HotelEvent();
}
class FetchEvent extends HotelEvent {
FetchEvent();
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
hotel_controller.dart
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
class HotelController {
final HotelService hotelService = HotelService();
Future<Hotels> parseHotels() async {
final response = await hotelService.fetchHotels();
final hotels = hotelsFromJson(response.body);
return hotels;
}
}
And finally the HomeScreen
home_screen.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hotels/blocs/hotel/hotel_bloc.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc()..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title').tr(),
),
body: BlocConsumer<HotelBloc, HotelState>(
listener: (context, state) {
if (state is HotelError) {
print(state.error);
}
},
builder: (context, state) {
if (state is HotelLoading) {
print('It\'s loading!');
}
if (state is HotelFinal) {
print(state.hotels.toString());
}
return Text('Default text');
},
),
);
}
}
The result is this:
The problem is the you haven't provided the BlocConsumer with your hotelBloc. You want to either have BlocProvider as a parent or use the cubit parameter on BlocConsumer.
BlocConsumer<HotelBloc, HotelState>(
cubit: hotelBloc,
listener:...
builder:...
)
Given the following Dart/Flutter class structure:
import 'package:flutter/material.dart';
class A with ChangeNotifier {
B _element1;
B _element2;
B get element1 => _element1;
B get element2 => _element2;
set element1(B value) {
_element1 = value;
notifyListeners();
}
set element2(B value) {
_element2 = value;
notifyListeners();
}
}
class B {
String x;
String y;
}
I am trying to listen to a change of A.element1.x but the problem is, the setter of class B can't call the notifyListeners() of the class A, so either I am Listening to A and won't notice a change or I am listening to B and I am losing the context to A.
I am using the Provider package in my Flutter project. But I am not sure, if I am misunderstanding the concept of Provider package or ChangeListeners. Either way I am not able to find a elegant solution.
Is there a possibility to overwrite the setter of class B from class A?
I could obviously implement a function for each element1 and element2 fields(x,y). But this is not good code style I guess.
Here's an example on how you could use callback functions. Without the callback, A won't call notifyListeners and your Home widget doesnt get rebuild. Here's a short video on what a VoidCallback is: https://www.youtube.com/watch?v=fWlPwj1Pp7U
Main function and a simple Home view:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'a.dart';
void main() {
runApp(
ChangeNotifierProvider<A>(
create: (context) => A(),
child: MaterialApp(
home: Home(),
),
)
);
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<A>(
builder: (context, model, child) {
return Scaffold(
body: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${model.element1.x}'),
RaisedButton(
child: Text("Set x of element1."),
onPressed: () {
Provider.of<A>(context, listen: false).element1.setX = 'Set to new value';
},
),
],
),
),
)
);
},
);
}
}
And then Class A:
import 'package:flutter/material.dart';
import 'package:tryout/b.dart';
class A extends ChangeNotifier {
B _element1 = B();
B _element2 = B();
B get element1 => _element1;
B get element2 => _element2;
A() {
_element1.callback = () => notifyListeners();
_element2.callback = () => notifyListeners();
}
set element1(B value) {
_element1 = value;
_element1.callback = () => notifyListeners();
notifyListeners();
}
set element2(B value) {
_element2 = value;
_element2.callback = () => notifyListeners();
notifyListeners();
}
}
And class B:
import 'package:flutter/cupertino.dart';
class B {
String x = "";
String y = "";
VoidCallback? callback;
B({
this.callback
});
set setX(String newValueX) {
x = newValueX;
if(callback != null) callback!();
}
void setY(String newValueY) {
y = newValueY;
if(callback != null) callback!();
}
}
I'd like to use Future teamMember() in another .dart file. Any contributions are welcome!
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
void main() {
runApp(new MaterialApp(
home: new Popups(),
));
}
class Popups extends StatefulWidget {
#override
_PopupsState createState() => _PopupsState();
}
class _PopupsState extends State<Popups> {
Future teamMember() async {
await showDialog(
context: context,
...
...
In the other class where you need it create a reference to your popup class and then call teamMember.
class OtherClass extends StatelessWidget {
Popups _popups = Popups();
.
.
RaisedButton(
child: Text("show team member popup"),
onPressed: (){
_popups.teamMember();
}
)
}
hope this helps