Flutter google map how to instanciate a GoogleMapController outside of the GoogleMap's widget onCreated function for testings?
Asked Answered
A

5

7

I'm using plugin google_maps_flutter 1.2.0, and i want to instanciate a GoogleMapController for testing some bloc's event, where the controller's value can't be null. Unfortunalty, i don't know how to do it, because to my knows, GoogleMapController can't be instanciate outside of the function 'OnCreated' of the GoogleMap() Widget.

 GoogleMap(onMapCreated: (GoogleMapController controller) {
                         // Can only be instanciate here
                        },
          );

It's impossible to do something like this :

  GoogleMapController controller = new GoogleMapsController();

I can only do this, and controller is null :

 GoogleMapController controller;

I tried multiple thing but nothing is working, can someone help me ?

Amalamalbena answered 22/2, 2021 at 0:2 Comment(0)
C
1

First, GoogleMapController _googleMapController in your _WidgetState.
Then

GoogleMap(onMapCreated: (GoogleMapController controller) {
        _googleMapController = controller;
    }
);  

After Google Map Created, you can use _googleMapController everywhere in your widget.

Chimborazo answered 22/2, 2021 at 3:13 Comment(5)
Thank you for your answer but i already know this (thats write in my post). My question is : is there another way to do it ? Because i need to insanciate a GoogleMapController in a test file, and i don’t know how to do it without load a map ?Amalamalbena
GoogleMapController is use to control the map. I don't know why you need a controller without a map. Without the map, controller is useless.Chimborazo
Need it for testing a bloc, because i gave it in argument the map controller.Amalamalbena
how to mock the google maps controller to do unit tests on it?Citizenry
You can try to make a GoogleMap() out of the widget tree and get controller from onMapCreated.Chimborazo
N
1

you can use like this - In your state:

final Completer<GoogleMapController> _controller =
      Completer<GoogleMapController>();

and in you GoogleMap() widget as this

 GoogleMap(
          myLocationButtonEnabled: false,
          zoomControlsEnabled: false,
          onMapCreated: (GoogleMapController controller) =>
              _googleMapController.complete(controller),
          initialCameraPosition: _initialCameraPosition),
Nominee answered 26/10, 2023 at 10:58 Comment(0)
T
0

You can use use _googleMapController? = controller; before the build method the make a little if statement (_googleMapController != null) where you want to use your method or what ever you want to do with the instance.

Tude answered 9/10, 2021 at 15:11 Comment(0)
Q
0

If you are using a Flutter 2.5.3 and later you can add a late in front of the variable declaration i.e late GoogleMapController _googleMapController;

In your googlemap widget you can use the variable as normal onMapCreated: (controller) => _googleMapController = controller,

Quixotic answered 22/10, 2021 at 6:21 Comment(0)
S
0

You have to use dependency injection to accomplish this. Take a look at the example below

Packages:

  • flutter_bloc: ^8.1.6
  • bdd_widget_test: ^1.7.4
  • mockito: ^5.4.4
  • google_maps_flutter: ^2.9.0
  • build_runner: ^2.4.11

map_page.feature

Feature: Map

    Background:
        Given The app is initialized
        And The page is displayed

    @success
    Scenario: User is centered on their location
        When I tap {Icons.my_location} button to get my current location
        Then The map is centered on my location

di.dart

void setupLocator() {
    // Any other injections...

    sl.registerFactory<Completer<GoogleMapController>>(
        () => Completer<GoogleMapController>(),
    );
};

map_page.dart

class MapPage extends StatefulWidget {
  const MapPage({super.key});

  @override
  State<MapPage> createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {
  final Completer<GoogleMapController> _controller =
      sl<Completer<GoogleMapController>>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocListener<MapBloc, MapState>(
        listener: _onGetLocation,
        child: GoogleMap(
          onMapCreated: (controller) => _controller.complete(controller),
          initialCameraPosition: const CameraPosition(
            target: LatLng(0, 0),
            zoom: 15,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.my_location),
        onPressed: () => context.read<MapBloc>().add(GetMyLocation()),
      ),
    );
  }

  void _onGetLocation(BuildContext context, MapState state) async {
    if (state is SuccessGetMyLocation) {
      var controller = await _controller.future;
      controller.animateCamera(CameraUpdate.newCameraPosition(
        CameraPosition(target: state.latLng, zoom: 15),
      ));
    }
  }
}

test_helper.dart

@GenerateNiceMocks(<MockSpec>[
  // Any other MockSpec...
  MockSpec<Completer<GoogleMapController>>(),
  MockSpec<GoogleMapController>(),
])
void main() {}

di_helper.dart

void setupDiHelper() {
    // Any other injections...

    sl.registerLazySingleton<Completer<GoogleMapController>>(
        () => MockCompleter(),
    );
};

the_app_is_initialized.dart

/// Usage: The app is initialized
Future<void> theAppIsInitialized(WidgetTester tester) async {
  PatrolTester $ = PatrolTester(
    tester: tester,
    config: const PatrolTesterConfig(),
  );
  await setupDiHelper();
  await $.pump();
}

the_page_is_displayed.dart

/// Usage: The page is displayed
Future<void> thePageIsDisplayed(WidgetTester $) async {
  await $.pumpWidget(
    MultiBlocProvider(
      providers: [
        BlocProvider<MapBloc>(
          create: (context) => sl<MapBloc>(),
        ),
      ],
      child: MaterialApp(
        home: const MapPage(),
      ),
    ),
  );
}

i_tap_button_to_get_my_current_location.dart

/// Usage: I tap {Icons.my_location} button to get my current location
Future<void> iTapButtonToGetMyCurrentLocation(
    WidgetTester $, IconData icon) async {
  var state = SuccessGetMyLocation(Position.fromMap(decode(Fixture.position)));
  when(sl<MapBloc>().state).thenReturn(state);

  // Create a mock GoogleMapController
  final mockController = MockGoogleMapController();

  // Stub the Completer's future to return the mock controller
  final completer = sl<Completer<GoogleMapController>>();
  when(completer.future).thenAnswer((_) async => mockController);

  // Stub the animateCamera method on the mock controller
  when(mockController.animateCamera(any)).thenAnswer((_) async => {});

  await $.tap(find.byIcon(icon));
  await $.pump();
}

the_map_is_centered_on_my_location.dart

/// Usage: The map is centered on my location
Future<void> theMapIsCenteredOnMyLocation(WidgetTester $) async {
  var controller = await sl<Completer<GoogleMapController>>().future;

  // Mock the position
  var position = Position.fromMap(decode(Fixture.position));
  var mockedLatLng = LatLng(position.latitude, position.longitude);
  var mockedScreenCoordinate = const ScreenCoordinate(x: 0, y: 0);

  // Add a stub for the getLatLng method
  when(controller.getLatLng(mockedScreenCoordinate))
      .thenAnswer((_) async => mockedLatLng);

  var latLng = await controller.getLatLng(mockedScreenCoordinate);

  expect(latLng, equals(mockedLatLng));
}

hooks.dart

abstract class Hooks {
  const Hooks._();

  static FutureOr<void> beforeEach(String title, [List<String>? tags]) {
    if (tags?.contains('success') ?? false) {
      provideDummy<MapState>(
        SuccessGetMyLocation(Position.fromMap(decode(Fixture.position))),
      );
    } else if (tags?.contains('failure') ?? false) {
    }
  }

  static FutureOr<void> beforeAll() {}

  static FutureOr<void> afterEach(
    String title,
    bool success, [
    List<String>? tags,
  ]) {}

  static FutureOr<void> afterAll() {}
}

map_page_test.dart

This file is autogenerated from map_page.feature using dart run build_runner build

Swinger answered 24/9, 2024 at 18:33 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.