Provider automatically gets updated when calling setState - flutter

I am new to Flutter and currently working with Providers. I am pulling some static array list from an api and saving it to the Provider. I am letting the user to select from this list and attach to the Item he is creating using the form.
So, Everytime the user tries to create a new Item, he/she should see the static list with the selection set to false.
But, the provider array variable gets automatically updated upon calling setState. Below is the Issue I'm facing..
main.dart
MultiProvider(
providers: [
ChangeNotifierProvider<Info1Class>(
create: (ctx) => Info1Class(),
),
ChangeNotifierProvider<Info2Class>(
create: (ctx) => Info1Class(),
),
],
child: MaterialApp(
And in my Stateful widget in the build method. I am getting the Provider Details like this.
screenArray.clear();
final t = Provider.of<Info1Class>(context, listen: false).dataArray;
screenArray.addAll(t);
Whenever I call setState to update the elements of screenArray, the provider data gets updated as well.
setState(() {screenArray[0].selected = true})
After setState(), if I print the Provider dataArray's first element, it showing as true.
print(Provider.of<Info1Class>(context, listen: false).dataArray[0].selected)
My Dependancies
provider: ^4.3.2+4
Is there a way to avoid the Provider data getting updated and only update the variable in my Stateful Widget ?
Please let me know if I am missing something.. Thanks for your help.

I tried Getx and Provider both for this problem, it's problem of referencing of object not the Provider or GetX,
I was coping reference of Objects, list or all data. to Solve this problem, I create clone of each object and then use it.
// COMMON
String? uid;
String? username;
String? fullName;
String? email;
num? timestamp;
List<ProfilePhoto>? photos;
List<String>? skills;
I add one object list and other simple String list
Clone class
MyUser.clone(MyUser? myUser) {
uid = myUser?.uid;
username = myUser?.username;
fullName = myUser?.fullName;
userType = myUser?.userType;
email = myUser?.email;
timestamp = myUser?.timestamp;
photos = ProfilePhoto.cloneList(myUser?.photos);
status = myUser?.status;
skills = [...(myUser?.skills ?? [])];
}
Constructor
MyUser({
this.uid,
this.username,
this.fullName,
this.email,
this.timestamp,
this.photos,
this.skills,)};
Photo class
class ProfilePhoto {
String? id;
String? title;
String? url;
File? file;
bool? isDefault;
ProfilePhoto({
this.id,
this.title,
this.url,
this.file,
this.isDefault,
});
ProfilePhoto.clone(ProfilePhoto profilePhoto) {
id = profilePhoto.id;
title = profilePhoto.title;
url = profilePhoto.url;
file = profilePhoto.file;
isDefault = profilePhoto.isDefault;
}
static List<ProfilePhoto>? cloneList(List<ProfilePhoto>? items) {
if (items == null) return [];
List<ProfilePhoto>? newItems = [];
for (ProfilePhoto item in items) {
newItems.add(ProfilePhoto.clone(item));
}
return newItems;
}
}
Screen layout
#override
void didChangeDependencies() {
super.didChangeDependencies();
final data = _userController.user;
MyUser? user = MyUser.clone(data);
}
void _onChangeDefault(int index) {
ProfilePhoto pp = _profilePhotos[index];
setState(() {
_profilePhotos.removeAt(index);
_profilePhotos.insert(0, pp);
});
}
this may be not a good or optimized solution, but this solve my problem of auto update data in state manager

Related

State in BLoC changes its value before calling emit(), but Equatable doesn't recognize the state change

My problem is that when I'm calling a Cubit function it changes a value before I hit emit. Because of that, the Cubit cannot detect that there was a change in the state and the emit() won't work.
I solved the problem by creating a random number each time the function is called so that the cubit can recognize the state change. I just want to know what I'm doing wrong here. I'm already using the Equatable package.
part of 'survey_cubit.dart';
abstract class SurveyState extends Equatable {
const SurveyState({
required this.business,
required this.locals,
this.local = Local.en,
this.status = BusinessStatus.setup,
this.review = Review.empty,
this.questionIndex = 0,
this.answersId = const [],
this.questionsId = const [],
this.random = 0.0,
});
final Business business;
final List<Local> locals;
final Local local;
final BusinessStatus status;
final Review review;
final int questionIndex;
final List<int> answersId;
final List<int> questionsId;
final double random;
#override
List<Object> get props => [
business,
locals,
local,
status,
review,
questionIndex,
answersId,
questionsId,
random,
];
SurveyState copyWith({
Business? business,
List<Local>? locals,
Local? local,
BusinessStatus? status,
Review? review,
int? questionIndex,
List<int>? answersId,
List<int>? questionsId,
double? random,
});
}
class SurveyInitial extends SurveyState {
const SurveyInitial({
required super.business,
required super.locals,
super.local = Local.en,
super.status = BusinessStatus.setup,
super.review = Review.empty,
super.questionIndex = 0,
super.answersId = const [],
super.questionsId = const [],
super.random = 0.0,
});
#override
SurveyState copyWith({
Business? business,
List<Local>? locals,
Local? local,
BusinessStatus? status,
Review? review,
int? questionIndex,
List<int>? answersId,
List<int>? questionsId,
double? random,
}) =>
SurveyInitial(
business: business ?? this.business,
locals: locals ?? this.locals,
local: local ?? this.local,
status: status ?? this.status,
review: review ?? this.review,
questionIndex: questionIndex ?? this.questionIndex,
answersId: answersId ?? this.answersId,
questionsId: questionsId ?? this.questionsId,
random: random ?? this.random,
);
}
class SurveyCubit extends Cubit<SurveyState> {
SurveyCubit(DeviceInfo deviceInfo)
: super(
SurveyInitial(
business: deviceInfo.business!,
locals: deviceInfo.locals,
),
);
void onRemoveReview(int questionId) {
final Review review = state.review;
review.reviewedQuestions.removeWhere(
(element) => element.questionId == questionId,
);
final List<int> questionsId = state.questionsId;
questionsId.remove(questionId);
emit(
state.copyWith(
review: review,
answersId: [],
questionsId: questionsId,
random: Random().nextDouble(),
),
);
print(state.questionsId);
}
}
In your cubit, you assign state.questionsId (the old state's list) to a new variable. This doesn't create a new list; it just adds a new reference to the old one. The object ID is still the same. When you emit the new state, Equatable looks at the object ID and sees that it's identical and thinks the two states are the same.
From the Bloc documentation:
Equatable properties should always be copied rather than modified. If
an Equatable class contains a List or Map as properties, be sure to
use List.from or Map.from respectively to ensure that equality is
evaluated based on the values of the properties rather than the
reference.
final List<int> questionsId = state.questionsId; should be final List<int> questionsId = List.from(state.questionsId);.

How to link up web api call to the list view

So i have my dart call to my api get method. Btw the way am just learning flutter and dart and trying out basic crud operations I would use to be doing in .net and c#
import 'dart:convert';
import 'package:theapp/models/Players.dart';
import 'package:http/http.dart';
class ApiService {
final String apiUrl = "https://apiurlhidden.com/api";
final String getAllPlayersEndPoint = "/GetAllPlayers/";
Future<List<Player>> getAllPlayers() async {
final getallPlayersUrl = Uri.parse(apiUrl + getAllPlayersEndPoint);
Response res = await get(getallPlayersUrl);
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<Player> players =
body.map((dynamic item) => Player.fromJson(item)).toList();
return players;
} else {
throw "Failed to load cases list";
}
}
}
And I have my listview here but it complaining saying key and players do not exist
import 'package:flutter/material.dart';
import 'package:theapp/models/Players.dart';
class PlayerList extends StatelessWidget {
List<Player> players = [];
PlayerList({Key key, this.players}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: players == null ? 0 : players.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {},
child: ListTile(
leading: Icon(Icons.person),
title: Text(players[index].firstName),
subtitle: Text(players[index].surname.toString()),
),
));
});
}
}
My Model
class Player {
final int id;
final int type;
final String playerLevel;
final String firstName;
final String surname;
Player(this.id, this.type, this.playerLevel, this.firstName, this.surname);
factory Player.fromJson(Map<String, dynamic> json) {
return Player(
json['id'],
json['type'],
json['playerlevel'],
json['firstname'],
json['surname'],
);
}
#override
String toString() =>
'Players{id: $id, firstName: $firstName, lastName: $surname}';
}
Is there any reason why it should not recognize players and key in my list view page also how do I get the items to appear in the listview.
Picture only added to show the context in the items I mentioned above. Also coming from a .net background I would normally use an observable collection so it gets any changes in data in real-time am I using the correct approach for that.
Use required keyword to make parameters mandatory.
PlayerList({required Key key, required this.players}) : super(key: key);
Named parameters are optional unless they’re explicitly marked as required.
See Parameters for details.

Flutter Riverpod: Filter rebuilds with StateNotifier and .select()

This is my current state management solution
class UserState {
final int id;
final String name;
}
class UserNotifier extends StateNotifier<UserState> {
UserNotifier() : super(User(1, 'Pero Peric'));
}
final userNotifierProvider = StateNotifierProvider((ref) => UserNotifier());
I want to rebuild my UI only when the name changes not the id!
Riverpod provides a way to do this link but I can't get it working with my StateNotifier.
I would write it like this but it isn't working like this.
// inside Consumer widget
final userName = watch(userNotifierProvider.select((value) => value.state.name));
Can you refactor my code to work or propose another solution?
Any help is appreciated!
According to the doc, "this method of listening to an object is currently only supported by useProvider from hooks_riverpod and ProviderContainer.listen".
Try to create another provider, which you can use in UI.
final nameProvider = StateProvider<String>((ref) => ref.watch(userNotifierProvider.select((user) => user.name)));
class UserState {
UserState({
required this.id,
required this.name,
});
final int id;
final String name;
}
class UserNotifier extends StateNotifier<UserState> {
UserNotifier() : super(UserState(id: 1, name: 'Pero Peric'));
}
final userNotifierProvider =
StateNotifierProvider<UserNotifier, UserState>((ref) => UserNotifier());
In consumer,
final userName =
watch(userNotifierProvider.select((value) => value.name)); // value is the state

How do I get mobx to update when I change a property in an ObservableList?

I currently have been using mobx for my flutter app, and I'm trying to update a ListTile to change it's colour onTap. Right now I have I have an ObservableList marked with #observable, and an #action that changes a property on an item in that list.
class TestStore = TestStoreBase with _$TestStore;
abstract class TestStoreBase with Store {
final DataService _dataService;
TestStoreBase({
#required DataService dataService,
}) : assert(dataService != null),
_dataService = dataService,
players = ObservableList<Player>();
#observable
ObservableList<Player> players;
#action
Future<void> loadPlayers(User user) async {
final userPlayers = await _dataService.getUserPlayers(user);
players.addAll(userPlayers);
}
#action
void selectPlayer(int index) {
players[index].isSelected = !players[index].isSelected;
);
}
}
in my UI I have this inside of a listbuilder:
return Observer(builder: (_) {
return Container(
color: widget.testStore.players[index].isSelected != null &&
widget.testStore.players[index].isSelected
? Colors.pink
: Colors.transparent,
child: ListTile(
leading: Text(widget.testStore.players[index].id),
onTap: () => widget.testStore.selectPlayer(index),
),
);
});
but it doesn't redraw when I call widget.testStore.selectPlayer(index);
The second thing I tried was to add #observable in the 'Players' class on the isSelected bool, but it doesn't seem to work either.
#JsonSerializable()
class Player {
final String id;
final bool isUser;
#observable
bool isSelected;
Player(this.id, this.isUser, this.isSelected);
factory Player.fromJson(Map<String, dynamic> data) => _$PlayerFromJson(data);
Map<String, dynamic> toJson() => _$PlayerToJson(this);
}
any help would be greatly appreciated, thanks!
Your are trying to take actions on the isSelected property, so basically you have to define the Player class as a MobX store as well to create a mixin that triggers reportWrite() on modifying isSelected.
Adding #observable annotation to players property only means to watch on the property itself, and typing players as a ObservableList means to watch on the list elements of the property, i.e. to watch on players[0], players[1]...and so on.
For example
#JsonSerializable()
class Player = _Player with _$Player;
abstract class _Player with Store {
final String id;
final bool isUser;
#observable
bool isSelected;
_Player(this.id, this.isUser, this.isSelected);
factory _Player.fromJson(Map<String, dynamic> data) => _$PlayerFromJson(data);
Map<String, dynamic> toJson() => _$PlayerToJson(this);
}
Here is a similar issue from MobX's GitHub repo: https://github.com/mobxjs/mobx.dart/issues/129

pass parameter to another component - null

Follow the excerpt that I send and the other that I receive the parameter. but it's coming null
Usuario usuario = Usuario();
usuario.email = email;
usuario.senha = senha;
//cadastrar ou logar
if( _cadastrar ){
print(usuario);
Navigator.of(context).pushNamed('/termos-aceite', arguments: usuario.email);
}else{
//Logar
_logarUsuario(usuario);
}
// second screen
Widget build(BuildContext context) {
setState(() {
args = ModalRoute.of(context).settings.arguments;
});
print('args');
print(args); //value is null
you need to send a map not a property
Usuario usuario = Usuario();
usuario.email = email;
usuario.senha = senha;
//cadastrar ou logar
if( _cadastrar ){
print(usuario);
Navigator.of(context).pushNamed('/termos-aceite', arguments: {"email":usuario.email});
}else{
//Logar
_logarUsuario(usuario);
}
when you try to get the values cast the arguments as Map of String Object like this and and use the property key you to get the data
setState(() {
args = ModalRoute.of(context).settings.arguments as Map<String, Object>;
});
print('args');
print(args["email"]); //value is email's value
There is a better and generic approach to do that, and I would suggest you to do that.
So we can do this via using the Constructor method, which accepts some variable with values, in your case this is the page, i.e., /termos-aceite. Now taking the Page name as Termos for this /termos-aceite. Let us see how this works.
class Termos extends StatelessWidget {
// Declare a field that holds the Todo.
final dynamic email;
// In the constructor, require a Todo.
DetailScreen({Key key, #required this.email}) : super(key: key);
//To use that, you can simply do
print(args);
setState(() {
args = this.email;
});
}
And In order to pass it to the page, in this case to Termos, we do this using your code only
Usuario usuario = Usuario();
usuario.email = email;
usuario.senha = senha;
//cadastrar ou logar
if( _cadastrar ){
print(usuario);
// Read about MaterialPageRoute() here
// https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html
Navigator.push(context, MaterialPageRoute(
builder: (context) => Termos(email: email)
));
}else{
//Logar
_logarUsuario(usuario);
}
I hope you will get it what you want. Also, please keep googling these things about flutter. It is cool, and you will find great things there in the documentation itself. I am anyway listing down some of the links for you, hope that would be a learning experience for you. Keep learning
Flutter Dev Docs
Naivgate With Argument
Passing data to a new screen