Search code examples
androidflutterdartgoogle-maps-markersmarker

BitmapDescriptor.fromBytes() is not working when trying to show custom marker on google maps


I am trying to show the custom images as markers on google map. Problem is BitmapDescriptor.fromAssetImage() is working fine but BitmapDescriptor.fromBytes() is not working. As I have to use canvas further I need to use BitmapDescriptor.fromBytes(). Any help is appreciated. Below is the complete code.

class AqiMapPage extends StatefulWidget {
  @override
  _AqiMapPageState createState() => _AqiMapPageState();
}

Future<BitmapDescriptor> getCustomMapMarker(int aqi) async {
  Levels levels = AqiCnAqiRange.getAqiLevel(aqi);
  // this is working
 /* return await BitmapDescriptor.fromAssetImage(
      ImageConfiguration(devicePixelRatio: 2.5),
      'assets/markers/${levels.markerIcon}');*/

  // this is not working
  return await _getAssetIcon(_context, 'assets/markers/${levels.markerIcon}');
}

Future<BitmapDescriptor> _getAssetIcon(BuildContext context, String imageUrl) async {
  final Completer<BitmapDescriptor> bitmapIcon =
  Completer<BitmapDescriptor>();
  final ImageConfiguration config = createLocalImageConfiguration(context);

   AssetImage(imageUrl)
      .resolve(config)
      .addListener(ImageStreamListener((ImageInfo image, bool sync) async {
    final ByteData bytes =
    await image.image.toByteData(format: ImageByteFormat.png);
    final BitmapDescriptor bitmap =
    BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
    bitmapIcon.complete(bitmap);
  }));

  return await bitmapIcon.future;
}

Future<Set<Marker>> getMarkerList(List<MapData> mapData) async {
  Set<Marker> markerList = Set();
  mapData.forEach((element) async {
    try {
      BitmapDescriptor bitmapDescriptor =
          await getCustomMapMarker(int.parse(element.aqi));
      Marker marker = new Marker(
        markerId: MarkerId(element.aqi),
        position: LatLng(element.lat, element.lon),
        icon: bitmapDescriptor,
      );
      markerList.add(marker);
    } catch (e) {
      print(e.toString());
    }
  });

  return markerList;
}

Completer<GoogleMapController> _controller;
BuildContext _context;
final _scaffoldKey = GlobalKey<ScaffoldState>();

class _AqiMapPageState extends State<AqiMapPage> {
  final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
      new GlobalKey<RefreshIndicatorState>();
  AqiCnMapBloc _bloc;

  @override
  void initState() {
    super.initState();
    _controller = Completer();
    _bloc = AqiCnMapBloc();
    _bloc.getAqiCnMap(false);
  }

  @override
  Widget build(BuildContext context) {
    _context = context;
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(AppLocalizations.of(context)
            .getString(AppStringKeys.AQI_MAP_PAGE_KEY)),
        centerTitle: true,
      ),
      backgroundColor: HexColor.fromHex(AppColors.scaffoldBackgroundColor),
      body: new RefreshIndicator(
        key: _refreshIndicatorKey,
        onRefresh: () => _bloc.getAqiCnMap(true),
        child: StreamBuilder<Response<AqiCnMapApiResponse>>(
          stream: _bloc.aqiCnMapStream,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              switch (snapshot.data.status) {
                case Status.LOADING:
                  return Loading(loadingKey: snapshot.data.message);
                  break;
                case Status.SUCCESS:
                  return AqiMapData(mapData: snapshot.data.data);
                  break;
                case Status.ERROR:
                  return Error(
                    error: snapshot.data.message,
                    onRetryPressed: () => _bloc.getAqiCnMap(false),
                  );
                  break;
              }
            }
            return Container();
          },
        ),
      ),
    );
  }
}

class AqiMapData extends StatelessWidget {
  final AqiCnMapApiResponse mapData;

  const AqiMapData({Key key, this.mapData}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Set<Marker>>(
      future: getMarkerList(mapData.mapData),
      builder: (BuildContext context, AsyncSnapshot<Set<Marker>> snapshot) {
        if (!snapshot.hasData) {
          // while data is loading:
          return  LoadingWithoutText();
        } else {
          // data loaded:
          return GoogleMap(
            mapType: MapType.hybrid,
            myLocationEnabled: true,
            // fixme  - fix lat lng
            initialCameraPosition:
                CameraPosition(target: LatLng(25.6185024, 85.0726964), zoom: 3),
            markers: snapshot.data,
            onMapCreated: (GoogleMapController controller) {
              _controller.complete(controller);
            },
          );
        }
      },
    );
  }
}

Solution

  • I have found a solution. Although I am not sure what is wrong with the above code. But it seems some kind of synchronization or rendering issue. And using setState() I am able to use BitmapDescriptor.fromBytes(). Below is my code.

    class AqiMapPage extends StatefulWidget {
      @override
      _AqiMapPageState createState() => _AqiMapPageState();
    }
    
    final _scaffoldKey = GlobalKey<ScaffoldState>();
    
    class _AqiMapPageState extends State<AqiMapPage> {
      final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
          new GlobalKey<RefreshIndicatorState>();
      AqiCnMapBloc _bloc;
    
      @override
      void initState() {
        super.initState();
        _bloc = AqiCnMapBloc();
        _bloc.getAqiCnMap(false);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          key: _scaffoldKey,
          appBar: AppBar(
            title: Text(AppLocalizations.of(context)
                .getString(AppStringKeys.AQI_MAP_PAGE_KEY)),
            centerTitle: true,
          ),
          backgroundColor: HexColor.fromHex(AppColors.scaffoldBackgroundColor),
          body: new RefreshIndicator(
            key: _refreshIndicatorKey,
            onRefresh: () => _bloc.getAqiCnMap(true),
            child: StreamBuilder<Response<AqiCnMapApiResponse>>(
              stream: _bloc.aqiCnMapStream,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  switch (snapshot.data.status) {
                    case Status.LOADING:
                      return Loading(loadingKey: snapshot.data.message);
                      break;
                    case Status.SUCCESS:
                      return AqiMapData(mapData: snapshot.data.data);
                      break;
                    case Status.ERROR:
                      return Error(
                        error: snapshot.data.message,
                        onRetryPressed: () => _bloc.getAqiCnMap(false),
                      );
                      break;
                  }
                }
                return Container();
              },
            ),
          ),
        );
      }
    }
    
    class AqiMapData extends StatefulWidget {
      final AqiCnMapApiResponse mapData;
    
      const AqiMapData({Key key, this.mapData}) : super(key: key);
    
      @override
      _AqiMapDataState createState() => _AqiMapDataState(mapData);
    }
    
    class _AqiMapDataState extends State<AqiMapData> {
      final AqiCnMapApiResponse mapData;
    
      _AqiMapDataState(this.mapData);
    
      Set<Marker> _markers = Set();
    
      Completer<GoogleMapController> _controller = Completer();
    
      static Future<Uint8List> getBytesFromAsset(String path, int width) async {
        ByteData data = await rootBundle.load(path);
        ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
            targetWidth: width);
        ui.FrameInfo fi = await codec.getNextFrame();
        return (await fi.image.toByteData(format: ImageByteFormat.png))
            .buffer
            .asUint8List();
      }
    
      populateMarkers() {
        mapData.mapData.forEach((element) async {
          try {
            final MarkerId markerId = MarkerId(element.uid.toString());
            Levels levels = AqiCnAqiRange.getAqiLevel(int.parse(element.aqi));
            final Uint8List markerIcon = await getBytesFromAsset('assets/markers/${levels.markerIcon}', 100);
    
            // creating a new MARKER
            final Marker marker = new Marker(
              icon: BitmapDescriptor.fromBytes(markerIcon),
              markerId: markerId,
              position: LatLng(element.lat, element.lon),
              infoWindow: InfoWindow(
                  title: element.station.name, snippet: element.station.time),
            );
            // the solution
            setState(() {
              // adding a new marker to map
              _markers.add(marker);
            });
          } catch (e) {
            print(e.toString());
          }
        });
      }
    
      @override
      void initState() {
        super.initState();
        populateMarkers();
      }
    
      @override
      Widget build(BuildContext context) {
        return GoogleMap(
          mapType: MapType.hybrid,
          myLocationEnabled: true,
          // fixme  - fix lat lng
          initialCameraPosition:
              CameraPosition(target: LatLng(25.6185024, 85.0726964), zoom: 3),
          markers: _markers,
          onMapCreated: (GoogleMapController controller) {
            _controller.complete(controller);
          },
        );
      }
    }