Implementing google map in Flutter just like Zomato or Swiggy

542

there is an Api called Streams in dart in import 'dart:async'; it is the same API that some state management libraries use under the hood.

we will uses StreamController and StreamBuilder to complete our work.

  1. create a stream controller of type LatLng

    StreamController<LatLng> streamController = StreamController();

  2. then in onCameraMove function inside the GoogleMap widget add LatLng to the streamController

    onCameraMove: (CameraPosition pos) {
        streamController.add(pos.target);
    }
  1. then consume the streams using StreamBuilder
StreamBuilder<LatLng>(
  stream: streamController.stream,
  builder: (context, snapshot) {
   if (snapshot.hasData) {
    return Column(
    children: [
      Text('${snapshot.data!.latitude}'),
      Text('${snapshot.data!.longitude}'),
                                        ],
     );
    } else {
   return const CircularProgressIndicator();
    }
    },
    )
  1. dispose / close it , to avoid any kind of memory leak
  @override
  void dispose() {
    streamController.close();
    super.dispose();
  }

full code : hope this works

import 'dart:async';
import 'package:geocoding/geocoding.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class NewAddressPage extends StatefulWidget {
  @override
  State<NewAddressPage> createState() => _NewAddressPageState();
}

class _NewAddressPageState extends State<NewAddressPage> {
  late GoogleMapController _controller;
  LatLng? markerPos;
  LatLng? initPos;
  Set<Marker> markers = {};
  TextEditingController? searchPlaceController;
  bool loadingMap = false;
  bool init = true;
  bool loadingAddressDetails = false;
  String addressTitle = '';
  String locality = '';
  String city = '';
  String state = '';
  String pincode = '';

  StreamController<LatLng> streamController = StreamController();

  void fetchAddressDetail(LatLng location) async {
    List<Placemark> placemarks =
        await placemarkFromCoordinates(location.latitude, location.longitude);
    addressTitle = placemarks[0].name!;
    locality = placemarks[0].locality!;
    city = placemarks[0].subLocality!;
    pincode = placemarks[0].postalCode!;
    state = placemarks[0].administrativeArea!;
  }

  getCurrentLoc() async {
    bool serviceEnabled;
    LocationPermission permission;
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }
    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error('Location permissions are denied');
      }
    }
    if (permission == LocationPermission.deniedForever) {
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }
    Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);
    initPos = LatLng(position.latitude, position.longitude);
    streamController.add(initPos as LatLng);
    setState(() {
      loadingMap = false;
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    loadingMap = true;
    getCurrentLoc();
    searchPlaceController = TextEditingController();
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
    streamController.close();

  }

  renderMap() {
    return SizedBox(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.width,
      child: (loadingMap)
          ? const Center(
              child: CircularProgressIndicator(),
            )
          : GoogleMap(
              zoomControlsEnabled: false,
              myLocationEnabled: true,
              buildingsEnabled: true,
              indoorViewEnabled: false,
              onMapCreated: (controller) {
                _controller = controller;
                setState(() {
                  fetchAddressDetail(initPos!);
                });
              },
              onCameraMove: (CameraPosition pos) {
                streamController.add(pos.target);
              },
              initialCameraPosition: CameraPosition(
                target: initPos!,
                zoom: 14.4746,
              ),
              mapType: MapType.normal,
            ),
    );
  }

  backButton() {
    return IconButton(
      onPressed: () {
        Navigator.pop(context);
      },
      color: Colors.black87,
      icon: const Icon(Icons.arrow_back),
    );
  }

  searchBox() {
    return Container(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.height,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15),
        border: Border.all(color: Colors.black87, width: 0.1),
        color: Colors.white,
      ),
      padding: const EdgeInsets.all(10),
      child: Center(
        child: TextFormField(
          controller: searchPlaceController,
          onChanged: (value) async {
            // ignore
          },
          decoration: const InputDecoration(
              contentPadding: EdgeInsets.all(8),
              border: InputBorder.none,
              hintText: "Search Places...",
              labelStyle: TextStyle(color: Colors.black87)),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
          appBar: AppBar(
            title: searchBox(),
          ),
          body: SizedBox(
            child: Stack(
              alignment: Alignment.center,
              children: [
                renderMap(),
                Positioned(
                    top: MediaQuery.of(context).size.height * 0.4,
                    child: Image.asset(
                      'assets/pin.png',
                      height: 30,
                      fit: BoxFit.cover,
                    )),
                Positioned(
                    bottom: 0,
                    child: Container(
                      height: MediaQuery.of(context).size.height * 0.2,
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(15),
                      decoration: const BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.only(
                              topRight: Radius.circular(10),
                              topLeft: Radius.circular(10))),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Row(
                            mainAxisAlignment: MainAxisAlignment.start,
                            children: [
                              Icon(
                                Icons.location_on,
                                color: Colors.red[200],
                                size: MediaQuery.of(context).size.width * 0.08,
                              ),
                              const Padding(padding: EdgeInsets.all(2)),
                              Column(
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    addressTitle,
                                    style: const TextStyle(
                                        overflow: TextOverflow.ellipsis,
                                        fontWeight: FontWeight.bold,
                                        fontSize: 10,
                                        color: Colors.black87),
                                  ),
                                  const Padding(padding: EdgeInsets.all(2)),
                                  Text(
                                    city,
                                    style: const TextStyle(
                                      fontSize: 6,
                                      color: Colors.black54,
                                    ),
                                  )
                                ],
                              )
                            ],
                          ),
                          const Padding(padding: EdgeInsets.all(10)),
                          Expanded(
                            child: InkWell(
                              onTap: () {
                                showModalBottomSheet(
                                    shape: const RoundedRectangleBorder(
                                        borderRadius: BorderRadius.vertical(
                                            top: Radius.circular(10.0))),
                                    backgroundColor: Colors.white,
                                    context: context,
                                    isScrollControlled: true,
                                    builder: (context) => const Text('ignore'));
                              },
                              child: Container(
                                decoration: BoxDecoration(
                                    color: Colors.red[400],
                                    borderRadius: BorderRadius.circular(5)),
                                height:
                                    MediaQuery.of(context).size.height * 0.07,
                                width: MediaQuery.of(context).size.width * 0.8,
                                child: Center(
                                    child: StreamBuilder<LatLng>(
                                  stream: streamController.stream,
                                  builder: (context, snapshot) {
                                    if (snapshot.hasData) {
                                      return Column(
                                        children: [
                                          Text('${snapshot.data!.latitude}'),
                                          Text('${snapshot.data!.longitude}'),
                                        ],
                                      );
                                    } else {
                                      return const CircularProgressIndicator();
                                    }
                                  },
                                )

                                    // Text(
                                    //   'Confirm Address',
                                    //   style: TextStyle(
                                    //     color: Colors.white,
                                    //     fontSize: 15,
                                    //   ),
                                    // ),
                                    ),
                              ),
                            ),
                          )
                        ],
                      ),
                    ))
              ],
            ),
          )),
    );
  }
}


Share:
542
Subhojeet Sahoo
Author by

Subhojeet Sahoo

Updated on January 04, 2023

Comments

  • Subhojeet Sahoo
    Subhojeet Sahoo over 1 year

    I am trying to implement a map in my flutter application that contains a marker which points to the center of the map and when we stop dragging the map , we get the latlong of the center position. Got inspired by Zomato and Swiggy's Map UI .enter image description here How to implement this ??

    import 'dart:async';
    import 'package:flutter/foundation.dart';
    import 'package:geolocator/geolocator.dart';
    import 'package:flutter/material.dart';
    import 'package:google_maps_flutter/google_maps_flutter.dart';
    
    void main() => runApp(const InitApp());
    
    class InitApp extends StatelessWidget {
      const InitApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: RenderMap(),
        );
      }
    }
    
    class RenderMap extends StatefulWidget {
      @override
      State<RenderMap> createState() => _RenderMapState();
    }
    
    class _RenderMapState extends State<RenderMap> {
      late Position _currentLocation;
      late GoogleMapController _controller;
      bool loadingMap = false;
      bool init = true;
    
      getCurrentLoc() async {
        bool serviceEnabled;
        LocationPermission permission;
        serviceEnabled = await Geolocator.isLocationServiceEnabled();
        if (!serviceEnabled) {
          return Future.error('Location services are disabled.');
        }
        permission = await Geolocator.checkPermission();
        if (permission == LocationPermission.denied) {
          permission = await Geolocator.requestPermission();
          if (permission == LocationPermission.denied) {
            return Future.error('Location permissions are denied');
          }
        }
        if (permission == LocationPermission.deniedForever) {
          return Future.error(
              'Location permissions are permanently denied, we cannot request permissions.');
        }
        _currentLocation = await Geolocator.getCurrentPosition(
            desiredAccuracy: LocationAccuracy.high);
        setState(() {});
      }
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        getCurrentLoc();
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return SafeArea(
          child: Scaffold(
              body: Container(
            height: MediaQuery.of(context).size.height,
            width: MediaQuery.of(context).size.width,
            child: Stack(
              alignment: Alignment.center,
              children: [
                (loadingMap)
                    ? const Center(
                        child: CircularProgressIndicator(),
                      )
                    : GoogleMap(
                        zoomControlsEnabled: false,
                        myLocationButtonEnabled: true,
                        indoorViewEnabled: false,
                        onMapCreated: (controller) {
                          _controller = controller;
                        },
                        initialCameraPosition: CameraPosition(
                          target: LatLng(_currentLocation.latitude,
                              _currentLocation.longitude),
                          zoom: 16,
                        ),
                        mapType: MapType.normal,
                      ),
                Image.asset(
                  'assets/pin.png',
                  height: 35,
                  fit: BoxFit.cover,
                ),
              ],
            ),
          )),
        );
      }
    }
    

    this is the source code , i am using a stack widget to hold the map and the marker at the center , now i want to know the latlong of the position that the pin is pointing to. Since the pin is at the center I just need to the the latlong of the cameraPosition .

    • Gerry
      Gerry about 2 years
      Welcome to SO! What have you tried so far? Add that to your question to get more relevant answers.
    • Subhojeet Sahoo
      Subhojeet Sahoo about 2 years
      @Gerry added the code , have a look.
  • Subhojeet Sahoo
    Subhojeet Sahoo about 2 years
    Thanks mate . I was struggling from last two days and didn't know the stream builder concepts .
  • narayann
    narayann about 2 years
    anytime mate . keep learning