skip to Main Content

I have two widgets that both use the same Future to display CircularProgressIndicators and then show the widget once the future completes. But the CircularProgressIndicators do not animate, so they are just small squares. They animate the very first time after a full compile, but they do not animate ever again, not even after an app refresh. I have tried with other animating widgets to confirm, and they are indeed static:

  @override
  Widget build(BuildContext context) {
    _mapLoadedNotifier = ref.read(mapLoadedProvider.notifier);
    _mapLoaded = ref.watch(mapLoadedProvider);
    theVm = ref.watch(searchPageVmProvider);

return Row(children: [ _mapOverlay(),_mapWidget(),]);
}


  Widget _mapOverlay() {
    return theVm.when(
        data: (store) {
          vm = store;

          if (!vm!.hasSearched) {
            vm!.isMapLoading = true;
          }
          vm!.ref = ref;
          return SearchFormBuilder(
              initState: vm!.initState,
              model: Search(search: SearchByTermSuggestion('')),
              builder: (context, formModel, child) {
                return Container(
                    padding: PAD_M_TOP,
                    alignment: Alignment.topLeft,
                    child: Container(
                        padding: const EdgeInsets.symmetric(horizontal: PAD_M),
                        width: sizingInfo.maxWidthS,
                        color: Colors.transparent,
                        child: VpCombinationAutocompleteField(
                            formControl: formModel.searchControl,
                            labelText: '',
                            hintText: 'Search',
                            widgetRef: vm!.ref!,
                            widgetOnSuggestionSelected:
                                (Suggestion suggestion) =>
                                    suggestion.onSelected())));
              });
        },
        error: (error, s) => Text(error.toString()),
        loading: () {
          return const Center(
            child: SizedBox(
                height: 30,
                width: 30,
                child: Center(child: CircularProgressIndicator())),
          );
        });
  }

  Widget _mapWidget() {
    return FutureBuilder<SearchPageVm>(
        future: ref.watch(searchPageVmProvider.future),
        builder: ((context, snapshot) {
          if (!snapshot.hasData) {
            return const Center(
              child: SizedBox(
                  height: 30,
                  width: 30,
                  child: Center(child: CircularProgressIndicator())),
            );
          }

          vm = snapshot.data;

          return StreamBuilder<dynamic>(
              stream: vm!.map$,
              builder: (context, AsyncSnapshot snapshot) {
                if (snapshot.hasData) {
                  vm!.vpMap = snapshot.data;
                  if (!vm!.hasSearched) {
                    vm!.isMapLoading = true;
                  }
                  vm!.ref = ref;
                }
                return _googleMap(vm!);
              });
        }));
  }

When I remove the StreamBuilder, they both animate correctly. This does not appear to be a riverPod issue, since I have tried plain Flutter FutureBuilders and it has the same issue. I’ve tried so many alternatives. The StreamBuilder stops the FutureBuilders CircularProgressIndicators from animating. Why?

It is the same issue as here:

Flutter CircularProgressIndicator() animation is not working inside Riverpod`s ref.watch().when

The provider:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:vepo/src/_common/extensions/vegan_item_establishments.dart';

import '../../../_common/constants/presentation/colors.dart';
import '../../../_common/enums/vegan_item_type.dart';
import '../../../_common/services/theme_mode.dart';
import '../../../_common/services/user.dart';
import '../../../application/local/form_capabilities.dart';
import '../../../application/local/map_data/map.dart';
import '../../../application/local/suggestion/search_by_term/search_by_term_suggestion.dart';
import '../../../application/search_params/vegan_item/vegan_item_search_params.dart';
import '../../../domain/vegan_item_establishment/_common/vegan_item_establishment.dart';
import 'search.dart';
import 'search_results_provider.dart';

final searchPageVmProvider = FutureProvider<SearchPageVm>((ref) async {
  final userWithLocation = await ref.watch(userWithLocationProvider.future);
  final vm = SearchPageVm(
    userWithLocation: userWithLocation,
  );
  vm.search();
  return vm;
});

class SearchPageVm {
  bool hasSearched = false;
  late GoogleMap? googleMapWidget;

  bool isMapLoading = true;
  VpMap? vpMap;
  WidgetRef? ref;
  ThemeMode? themeMode;
  Color? statusBarColor;
  String? searchText;
  Stream<VpMap>? map$;
  late final StreamController<VpMap> mapController =
      StreamController.broadcast();
  late UserWithLocation _userWithLocation;
  Set<Marker> markers = <Marker>{};
  Stream<String?>? searchTerm;
  GoogleMapController? googleMapController;

  SearchPageVm({required UserWithLocation userWithLocation}) {
    map$ = mapController.stream;
    _userWithLocation = userWithLocation;
  }
  @override
  Search get model => Search(search: SearchByTermSuggestion(''));
  Position get userPosition => _userWithLocation.userPosition;

  /// Add an array of search results to the map
  void addResults(Iterable<VgnItmEst> searchResults) {
    mapController.add(VpMap(
        position: _userWithLocation.userPosition,
        markers: searchResults.markers));
    _animateCamera(searchResults);
  }

  @override
  void initState(BuildContext context, SearchForm formModel, [dynamic args]) {
    if (!hasSearched) {
      search();
      hasSearched = true;
    }
    themeMode = ref?.read(themeModeServiceProvider);
    statusBarColor = themeMode == ThemeMode.light
        ? BACKGROUND_COLOR
        : DARK_THEME_BACKGROUND_COLOR_LIGHT;
  }

  /// Search for results and add them to the map
  Future<List<VgnItmEst>?> search(
      {String searchTerm = '', VgnItmType? category}) async {
    final searchResults =
        await _search(searchTerm: searchTerm, category: category);
    addResults(searchResults ?? []);
    return searchResults;
  }

  void _animateCamera(Iterable<VgnItmEst> searchResults) {
    googleMapController?.animateCamera(CameraUpdate.newCameraPosition(
        CameraPosition(
            target: LatLng(
                searchResults.first.establishment!.location!.latitude,
                searchResults.first.establishment!.location!.longitude),
            zoom: 13)));
  }

  Future<List<VgnItmEst>?> _search(
      {required String searchTerm, VgnItmType? category}) async {
    final result = await ref?.watch(searchResultsProvider(VgnItmSearchParams(
            searchTerm: searchTerm,
            page: 1,
            pageSize: 10,
            vgnItmDiscriminators: category != null ? [category.name] : null))
        .future);
    return result;
  }
}

final searchResultsProvider =
    FutureProvider.family<List<VgnItmEst>, VgnItmSearchParams>(
        (ref, searchParams) async {
  List<VgnItmEst>? searchResults;

  final allVgnItmsGooglePlacesRepo = ref.read(allVgnItmEstsRepoProvider);

  searchResults = (await allVgnItmsGooglePlacesRepo.search(searchParams))?.data;

  return searchResults!;
});

2

Answers


  1. The way you handle different state of your FutureBuilder is wrong, also when you use FutureBuilder, you should use ref.read, you need to change your _mapWidget to this:

    Widget _mapWidget() {
        return FutureBuilder<SearchPageVm>(
            future: ref.read(searchPageVmProvider.future), // <=== change this
            builder: (context, snapshot) { // <=== change this
              switch (snapshot.connectionState) {
                case ConnectionState.waiting:
                  return const Center(
                    child: SizedBox(
                        height: 30,
                        width: 30,
                        child: Center(child: CircularProgressIndicator())),
                  );
                default:
                  if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                  } else {
                    vm = snapshot.data;
    
                    return StreamBuilder<dynamic>(
                        stream: vm!.map$,
                        builder: (context, AsyncSnapshot snapshot) {
                          if (snapshot.hasData) {
                            vm!.vpMap = snapshot.data;
                            if (!vm!.hasSearched) {
                              vm!.isMapLoading = true;
                            }
                            vm!.ref = ref;
                          }
                          return _googleMap(vm!);
                        });
                  }
              }
            });
      }
    
    Login or Signup to reply.
  2. Just Make your own progress dialog for just do single change and change will reflect whole code.

    class ProgressBar extends StatelessWidget {
      final Color color;
    
      ProgressBar({this.color = Colors.amber});
      Widget build(BuildContext context) {
        return Center(
          child: SizedBox(
            height: 40,
            width: 40,
            child: CircularProgressIndicator(color:color))
        );}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search