I need to let the consumer widget listen to multiple variables depending on a boolean value.
this is the model class
class Lawyer{
Data? data;
double? distance = 0;
Lawyer({this.data, this.distance});
factory Lawyer.fromJson(Map<String, dynamic> json) =>
Lawyer(data: Data.fromJson(json['listing_data']));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
class Data{
String? title;
String? email;
String? phone;
Location? location;
List<String>? logo;
List<String>? cover;
Data({this.title, this.email, this.phone, this.logo, this.cover, this.location});
factory Data.fromJson(Map<String, dynamic> json) {
var logo = json['_job_logo'];
var cover = json['_job_cover'];
var long = json['geolocation_long'];
var lat = json['geolocation_lat'];
return Data(title: json['_job_tagline'], email: json['_job_email'],
location: Location(latitude: json['geolocation_lat'], longitude: json['geolocation_long']),
phone: json['_job_phone'], logo: List<String>.from(logo),
cover: List<String>.from(cover)
);
}
}
and this is the view model notifier
class LawyerAPIServices extends ChangeNotifier{
final url = "https://dalilvision.com/wp-json/wp/v2/job_listing";
List<Lawyer> lawyersList = [];
List<Lawyer> staticLawyersList = [];
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = lawyersList;
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
Future<List<Lawyer>> getFullListOfLawyers() async {
notifyListeners();
print('fulll list: ${staticLawyersList.length}');
return staticLawyersList;
}
}
and finally this is the consumer widget
Consumer<LawyerAPIServices>(
builder: (context, value, child) => FutureBuilder(
future: _list,
builder: (BuildContext context, AsyncSnapshot<List<Lawyer>> snapshot) {
if (snapshot.hasData){
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) => const Divider(color: Colors.transparent),
itemCount: value.lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: snapshot.data![index].data!.title!,
email: snapshot.data![index].data!.email!,
phone: snapshot.data![index].data!.phone!,
logo: snapshot.data![index].data!.logo![0],
cover: snapshot.data![index].data!.cover![0]
),
);
}
}
);
}
else if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString())
);
}
else {
return const CircularProgressIndicator(
strokeWidth: 2,
);
}
},
),
)
In the notifier class
there are two lists, the staticLawyerList
is initialized only once when getting the list from a network call and then used as a backup list, and the lawyersList
is the one that will be manipulated.
what I have done until now is to get the initial value of lawyersList
by a network call, then somehow the staticLawyersList
values are always equal to lawyersList
, even if I made any change or manipulate the lawyersList
these changes will automatically reflect on the staticLawyersList
which is really weird.
now what I want to achieve exactly is to apply a condition to update the UI with the appropriate list depending on this condition.
if(setByPosition == false){
//update UI with `staticLawyersList`
}
else {
//update UI with `lawyersList`
}
update!!!!!!!!
here's how I update my consumer
CheckboxListTile(
activeColor: Colors.black,
value: isChecked,
onChanged: (value) async {
saveSharedPreferences(value: value!);
if(value == true) {
Provider.of<LawyerAPIServices>(context, listen: false).sortLawyersList(
devicePosition: widget.position, lawyersList: widget.list);
}
else{
Provider.of<LawyerAPIServices>(context, listen: false).getFullListOfLawyers();// the list returned by this function don't applied to the consumer
}
setState(() {
isChecked = value;
Navigator.pop(context);
});
},
title: const Text('Filter by distance'),
),
CodePudding user response:
A few things to consider:
When you do this "staticLawyersList = lawyersList" you actually have two "pointers" to the same list. It works that way for lists, sets, classes, etc.. only basic types as int, double, string are really copied. You can use this instead: "staticLawyersList = List.from(lawyersList);"
It doesn't seem you need the ChangeNotifier in your LawyerAPIServices. You could create an instance of LawyerAPIServices in the widget you need it and call fetchLawyers. Do it in the initState of a StatefullWidget if you don't want the list to be rebuilt multiple times. In your build method use a FutureBuilder to read the Future and decide what to show in the UI.
class _MyWidget extends State<MyWidget> {
late final LawyerAPIServices lawyerApi;
// Create this variable to avoid calling fetchLawers many times
late final Future<List<Lawyer>> lawyersList;
@override
void initState() {
super.initState();
// Instantiate your API
lawyerApi = LawyerAPIServices();
// This will be called only once, when this Widget is created
lawyersList = lawyerApi.fetchLawyers();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Lawyer>>(
future: lawyersList,
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerApi.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
);
}
Widget _listView(List<Lawyer> lawyersList) {
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) =>
const Divider(color: Colors.transparent),
itemCount: lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: lawyersList[index].data!.title!,
email: lawyersList[index].data!.email!,
phone: lawyersList[index].data!.phone!,
logo: lawyersList[index].data!.logo![0],
cover: lawyersList[index].data!.cover![0]),
);
});
}
}
If for any reason you need to share the same LawyerAPIServices across multiple widgets, you could instantiate it on the top of your tree and send it down using Provider or as a parameter.
The method getFullListOfLawyers doesn't need to return a Future, since staticLawyersList is a List (not a Future). You could get this list directly using "LawyerAPIServices.staticLawyersList" or maybe something like this could make sense:
Future<List> getFullListOfLawyers() async { if(staticLawyersList.isEmpty) { await fetchLawyers(); } print('fulll list: ${staticLawyersList.length}'); return Future.value(staticLawyersList); }
CodePudding user response:
as @Saichi-Okuma said that to copy the content of a list you should use staticLawyersList = List.from(lawyersList)
because in dart and most of the java compiler programming languages when you use staticLawyersList = lawyersList
this means that you are referring to the lawyersList
by the staticLawyersList
.
then I manipulate the lawyersList
as I want with help of staticLawyersList
lawyersList.clear();
lawyersList.addAll(staticLawyersList);
But when I did so, the consumer didn't apply the changes based on the staticLawyersList
although the logcat shows that the staticLawyersList
length is 10 which is what I want (full list without filtration).
the conclusion of my problem can be listed in two points:
1- the consumer is listening to only one list lawyersList
and I think it still exists.
2- the pointer problem as @Saichi-Okuma mentioned.
here are the full code changes
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = List.from(lawyersList);// use this statment instead of staticLawyersList = lawyersList
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
CodePudding user response:
The Consumer Widget gets rebuild every time you call notify notifyListeners, regardless the state of any lists.
Maybe you are not accessing the Instance of the API being consumed. Make sure you are using the 2nd parameter of the Consumer builder.
Consumer<LawyerAPIServices>(builder: (context, lawyerAPI, child) =>
FutureBuilder(
future: lawyerAPI.fetchLawyers(),
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerAPI.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
I don't think you need the code below for this particular need. It'd override your lawyersList and notify to all listeners even though nothing really changed. Just access your staticLawyersList directly, since it was populated when you called fetchLawyers.
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}