3

Building an application with Flutter and liking it so far, but an annoying problem is bugging me.

I have a "main" Stateful Widget with a list of shops in attribute, a search bar, a "child" widget which is either a map or a list, and we can switch between views by clicking on a button. There is an Inherited widget wrapped inside the main widget so we can get state of the main widget and thus the list.

I have a listener on the search bar :

void updateNameFilter(){
  setState(() {
    FiltersService.nameSearch=_searchTextController.text;
  });}

It works perfectly and smoothly with the list widget (which has a FutureBuilder) where we filter the list.

child: TruckList(snapshot.data.where((Truck t)=>FiltersService.shouldDisplay(t)).toList(), _handleRefresh),

However, for the maps widget, we use the Google Maps plugin. At the creation of the map, we add a marker on it for every item of the list that we should display :

void _onMapCreated(GoogleMapController controller) async {
mapController = controller;

 //Get the list of trucks from parent widget
List<Truck> list= await TrucksMainWidget.of(context).trucksList;
//Iterate on all trucks to add them on the map
list.where((Truck t) => FiltersService.shouldDisplay(t)).forEach((Truck t) => _addTruckMarker(t)); }

Then, everytime the value in the search bar changes, it rebuild the whole widget and it is very unefficient, with an unesthetic black screen at rebuild. Moreover, it works once in a few times : most of the time, the widget is not even rebuilding, making the results of the search wrong.

One strange thing is that when I launch the app in debug mode and I set a breakpoint on the "build" method of the map widget, it works as expected.

What I would like to do is, when the list changes in the main widget : -If we are on the list widget, rebuild -If we are on the Google maps widget : trigger a function that will erase all markers and re-add them given the new list, WITHOUT rebuilding the whole widget.

I am sure there is a way to do this, but I have been trying for a few days now and I can't find a way. Maybe someone can help me ? Thanks a lot ! :)

EDIT : Here is my code :

Inherited Widget

class _TrucksInherited extends InheritedWidget {
  _TrucksInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final _TrucksMainWidgetState data;

  @override
  bool updateShouldNotify(_TrucksInherited oldWidget) {
    return true;
  }
}

class TrucksMainWidget extends StatefulWidget {
  final Widget child;

  TrucksMainWidget({this.child});

  @override
  _TrucksMainWidgetState createState() => _TrucksMainWidgetState();

  static _TrucksMainWidgetState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(_TrucksInherited)
            as _TrucksInherited)
        .data;
  }
}

class _TrucksMainWidgetState extends State<TrucksMainWidget> {
  final Icon _mapIcon = new Icon(
    FontAwesomeIcons.map,
    color: Color(0xFF666666),
  );
  final Icon _listIcon = new Icon(
      FontAwesomeIcons.thLarge, 
      color: Color(0xFF666666)
  );
  Widget _trucksListWidget;
  Widget _trucksMapWidget;

  TextEditingController _searchTextController;
  Icon _switchIcon;
  Widget _widgetView;

  Future<List<Truck>> _trucksList;

  @override
  void initState() {
    super.initState();
    _trucksList = TruckService.getAll();
    _trucksListWidget = new TrucksListWidget();
    _trucksMapWidget = new TrucksMapWidget();

    _searchTextController = new TextEditingController();
    _searchTextController.addListener(updateNameFilter);
    _switchIcon = _mapIcon;
    _widgetView = _trucksListWidget;
  }

  Future<List<Truck>> get trucksList => _trucksList;

  @override
  void dispose(){
    _searchTextController.removeListener(updateNameFilter);
    _searchTextController.dispose();
    super.dispose();
  }

  void updateNameFilter(){
    setState(() {
      FiltersService.nameSearch=_searchTextController.text;
    });
  }

  void refresh() {
    setState(() {
      _trucksList = TruckService.getAll();
    });
  }

  void _onSwitchView() {
    setState(() {
      if (_switchIcon.icon == FontAwesomeIcons.map) {
        _switchIcon = _listIcon;
        _widgetView = _trucksMapWidget;
      } else {
        _switchIcon = _mapIcon;
        _widgetView = _trucksListWidget;
      }
    });
  }

  _openFiltersDialog() async {
    bool shouldUpdate = await showDialog(
      barrierDismissible: false,
      context: context,
      builder: (BuildContext context) {
        return FiltersAlertBox();
      },
    );
    if (shouldUpdate != null && shouldUpdate) {
      setState(() {});
    }
  }

  Widget build(BuildContext context) {
    return Scaffold(
        resizeToAvoidBottomPadding: false,
        appBar: AppBar(
          backgroundColor: Colors.white,
          title: Padding(
            padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
            child: Directionality(
                textDirection: Directionality.of(context),
                child: TextField(
                  style: new TextStyle(color: Colors.black, fontSize: 16.0),
                  decoration: InputDecoration(
                      hintText: 'Recherche',
                      hintStyle: new TextStyle(color: Color(0xFF999999)),
                      fillColor: Color(0xFFF6F6F6),
                      filled: true,
                      border: InputBorder.none,
                      prefixIcon: Icon(Icons.search, color: Color(0xFF999999))),
                  autofocus: false,
                  controller: _searchTextController,
                )),
          ),
          actions: <Widget>[
            IconButton(
              icon: Icon(
                FontAwesomeIcons.slidersH,
                color: Color(0xFF666666),
              ),
              onPressed: _openFiltersDialog,
            ),
            IconButton(
              icon: _switchIcon,
              onPressed: _onSwitchView,
            )
          ],
        ),
        body: _TrucksInherited(
          data: this,
          child: _widgetView,
        ));
  }
}

List Widget (OK)

class TrucksListWidget extends StatefulWidget {

  TrucksListWidget();

  _TrucksListWidgetState createState() => new _TrucksListWidgetState();

}

class _TrucksListWidgetState extends State<TrucksListWidget>{

  Future<List<Truck>> _handleRefresh() async{
    final state = TrucksMainWidget.of(context);
    state.refresh();
    return state.trucksList;
  }

  Widget build(BuildContext context){
    final state = TrucksMainWidget.of(context);
    return FutureBuilder<List<Truck>>(
          future: state.trucksList,
          builder: (BuildContext context, AsyncSnapshot snapshot){
            if(snapshot.hasData) {
              return RefreshIndicator(
                onRefresh: _handleRefresh,
                child: TruckList(snapshot.data.where((Truck t)=>FiltersService.shouldDisplay(t)).toList(), _handleRefresh),
              );
            }else if(snapshot.hasError){
              return Center(
                child: Text('${snapshot.error}'),
              );
            }
            return Center(
              child: CircularProgressIndicator(),
            );
          }
    );
  }
}

Map Widget (KO)

class TrucksMapWidget extends StatefulWidget {
  _TrucksMapWidgetState createState() => new _TrucksMapWidgetState();
}

class _TrucksMapWidgetState extends State<TrucksMapWidget> {

  Future<Position> userPosition;
  GoogleMapController mapController;
  Map<Marker, Truck> allMarkers = {};
  StreamSubscription subscription;

  void initState(){
    super.initState();
    initPosition();
    subscription = LocationService.ctrl.stream.listen((Future<Position> p)=>userPosition=p); //Listening LocationService's stream for a change of the user position
  }

  void dispose(){
    super.dispose();
    subscription.cancel();
  }

  void initPosition() async{
   userPosition = LocationService.getUserPosition();
  }

  void _onMapCreated(GoogleMapController controller) async {
    mapController = controller;

     //Get the list of trucks from parent widget
    List<Truck> list= await TrucksMainWidget.of(context).trucksList;
    //Iterate on all trucks to add them on the map
    list.where((Truck t) => FiltersService.shouldDisplay(t)).forEach((Truck t) => _addTruckMarker(t));

  }



  void _addTruckMarker(Truck truck) async {
      Marker marker = await mapController.addMarker(MarkerOptions(
        icon: BitmapDescriptor.fromAsset(
          truck.situation.isOpen ? 'assets/images/open_marker.png' : 'assets/images/close_marker.png',
        ),
        infoWindowText: InfoWindowText(truck.name, truck.situation.address),
        position: LatLng(truck.situation.position.lat,
            truck.situation.position.long),
        consumeTapEvents: true,
      ));
      allMarkers[marker] = truck; //Linking the marker to its truck in the Map
  }

  Widget build(BuildContext context) {
    return FutureBuilder(
      future:userPosition,
      builder: (context, snapshot){
        if(snapshot.connectionState==ConnectionState.done){
          return _getMap(snapshot.data);
        }else{
          return Center(
            child : CircularProgressIndicator(),
          );
        }

      },
    );
  }

  Widget _getMap(Position pos){
    return GoogleMap(
      onMapCreated: _onMapCreated,
      options: GoogleMapOptions(
        myLocationEnabled: true,
        cameraPosition: CameraPosition(
          target: LatLng(pos.lat, pos.long), //Starting position at user
          zoom: 8.0,
        ),
      ),
    );
  }

}
C. Grd
  • 68
  • 5

0 Answers0