1

So am getting data from api and then based on my search field and search type I update the UI.

For State management am using riverpod.

this is my code

final countriesProvider =
 StateNotifierProvider<CountriesProvider, List<Country>>((ref) {
 return CountriesProvider([]);
});
final futureCountriesProviderFiltred = FutureProvider<List<Country>>(
 (ref) async {
 final countries = ref.watch(countriesProvider);
 final searchType = ref.watch(searchTypeProvider);
 final search = ref.watch(searchProvider);
 print(search); // here
 print(countries.isEmpty);
 if (countries.isEmpty) {
 await ref.read(countriesProvider.notifier).fetchAndSetCountries();
 return countries;
 } else {
 if (searchType == SearchType.name) {
 await ref
 .read(countriesProvider.notifier)
 .fetchAndSetCountriesByName(search);
 return countries;
 } else {
 await ref
 .read(countriesProvider.notifier)
 .fetchAndSetCountriesByRegion(search);
 return countries;
 }
 }
 },
);
final searchTypeProvider = StateProvider<SearchType>((ref) {
 return SearchType.name;
});
enum SearchType { name, region }
final searchProvider = StateProvider<String>((ref) {
 return '';
});
class CountriesProvider extends StateNotifier<List<Country>> {
 final CountriesServices _countriesServices = CountriesServices();
 CountriesProvider(super.state);
 Future<void> fetchAndSetCountries() async {
 try {
 final List<Country> loadedCountries =
 await _countriesServices.getCountries();
 state = loadedCountries;
 } catch (error) {
 if (error == 'Country not found') {}
 }
 }
 Future<void> fetchAndSetCountriesByName(String name) async {
 try {
 final List<Country> loadedCountries =
 await _countriesServices.getCountriesByName(name);
 state = loadedCountries;
 } catch (error) {
 if (error == 'Country not found') {}
 }
 }
 Future<void> fetchAndSetCountriesByRegion(String region) async {
 try {
 final List<Country> loadedCountries =
 await _countriesServices.getCountriesByRegion(region);
 state = loadedCountries;
 } catch (error) {
 if (error == 'Country not found') {}
 }
 }
}

the problem is that the UI is never done loading when there is value in searchProvider once the value is back to "" empty string, suddenly, it's done loading

also when there is value in searchProvider the print statement got called to many times till the value in searchProvider back to "" empty string then it stops

this is the UI code


Widget build(BuildContext context, WidgetRef ref) {
 final countriesProvider = ref.watch(futureCountriesProviderFiltred); 
 return Scaffold(
 appBar: AppBar(
 title: Center(
 child: SizedBox(
 child: SearchBar(
 onChanged: (value) {
 ref.read(searchProvider.notifier).state = value;
 },
 trailing: [
 SizedBox(
 child: DropdownButton<String>(
 items: <SearchType>[SearchType.name, SearchType.region]
 .map<DropdownMenuItem<String>>((SearchType value) {
 return DropdownMenuItem<String>(
 value: value.toString(),
 child: Text(
 value.toString(),
 style: TextStyle(fontSize: 15),
 ),
 );
 }).toList(),
 onChanged: (String? newValue) {
 if (newValue == SearchType.name.toString())
 ref.read(searchTypeProvider.notifier).state =
 SearchType.name;
 else
 ref.read(searchTypeProvider.notifier).state =
 SearchType.region;
 },
 ),
 ),
 ],
 
 )),
 ),
 ),
 body: countriesProvider.when(
 data: (countries) {
 return ListView.builder(
 itemCount: countries.length,
 itemBuilder: (context, index) {
 return CountryCard(
 country: countries[index],
 );
 },
 );
 },
 loading: // ui for loading,
 error: (error, stackTrace) => Center(
 child: Text(error.toString()),
 ),
 ),
 );

I tried multiple things but I could not solve it. I know that watch will be listening for any changes in the provider but in the UI I only update it once for every letter the user write (onChanged).

So am just change the state of the searchProvider once. but what I noticed that once the state is not empty string the futureCountriesProviderFiltred provider will updated infinitely.

is it because of this code here?

 await ref
 .read(countriesProvider.notifier)
 .fetchAndSetCountriesByName(search);

And I tried to hard code the value in searchProvider and once I launch the app it will get stuck at loading

final searchProvider = StateProvider<String>((ref) {
 return 'USA';
});

enter image description here

updated

I reduced the code of futureCountriesProviderFiltred to

final futureCountriesProviderFiltred = FutureProvider<List<Country>>(
 (ref) async {
 final countries = ref.watch(countriesProvider);
 await ref.read(countriesProvider.notifier).fetchAndSetCountries();
 return countries;
 },
);

and still print get called many times with loading UI!

asked Jun 25, 2023 at 11:23
2
  • consider using onchangeend property Commented Jun 25, 2023 at 11:42
  • I did not get what you mean Commented Jun 25, 2023 at 11:44

2 Answers 2

0

You need to change your logic. You have a circular addiction going on:

start in widget -> final countriesProvider = ref.watch(futureCountriesProviderFiltred)
-> ref.watch(countriesProvider) in futureCountriesProviderFiltred
-> update state `countriesProvider` when call onChanged(){}
if (countries.isEmpty) {
 await ref.read(countriesProvider.notifier).fetchAndSetCountries();
}

and the circle is closed, because at this point the futureCountriesProviderFiltred update will happen. And because of countries.isEmpty it happens exactly as you described.

answered Jun 25, 2023 at 11:50
Sign up to request clarification or add additional context in comments.

Comments

0

This is broken:

await ref.read(countriesProvider.notifier).fetchAndSetCountries();

Never mutate another provider during the build/create of a provider. There are lints to detect this, and it will almost always lead to circular builds.

You need to have a proper Directed Acyclic Graph for your provider dependencies. Mutations are essentially backward-pointing (that depends on this) links, and spoil your DAG.

answered Jun 25, 2023 at 18:46

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.