0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】ネイティブ連携

Last updated at Posted at 2024-07-22

前提(バンドルIDについて)

組織名(Organization): com.example
プロジェクト名(Project name): myapp
の場合、
バンドルID: com.example.myapp

MethodChannel(双方向通信)

Flutter側からNative側へメソッドの呼び出しを行い、Native側はそのメソッドを実行後、結果やデータをFlutter側へ返却する
"バンドルID/~"のMethodChannel文字列がFlutter側とNative側で一致している必要がある。

■用途
任意のタイミングでネイティブの機能を利用
このチャンネルを通じて、Flutterアプリケーションはネイティブプラットフォームの機能(カメラアクセス、位置情報サービスなど)を利用できる

■iOS側の操作

iOS/Runner/AppDelegate.swiftを開く
「Open iOS/macOS module in Xcode」というリンクがあるのでクリックするとXcodeで開ける
開いたXcodeでAppDlegateとinfo.plistを編集
image.png

■Android側の操作
以下のMainActivity/AndroidManifest.xmlを編集

image.png

■位置情報を取得する実装例

<Flutter>

gps_loation_repository.dart
import 'dart:async';

import 'package:flutter/services.dart';
import './location_repository.dart';

/// GPSを使った位置情報を取得するための仕組み。
class GpsLocationRepository extends LocationRepository {
  static const platform = MethodChannel('com.example.multios/location');

  // 2) ここに後ほどEventChannelの処置を追加

  /// 1) MethodChannelを使って位置情報を取得します。
  @override
  Future<Location> get() async {
    final String result = await platform.invokeMethod('getLocation');
    final splitted = result.split(',');
    return Location(double.parse(splitted[0]), double.parse(splitted[1]));
  }

/// 2) EventChannelを使って位置情報を監視します。(後ほど追加)
}

<iOS>

AppDelegate.swift
swift AppDelegate.swift
import UIKit
import Flutter
import CoreLocation

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate {
    //    @objc class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate, FlutterStreamHandler  {
    private var locationChannel: FlutterMethodChannel?
    private var locationManager: CLLocationManager!
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        // ここから追加
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        locationChannel = FlutterMethodChannel(name: "com.example.multios/location",
                                               binaryMessenger: controller.binaryMessenger)
        locationChannel?.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            if call.method == "getLocation" {
                self.getLocation(result: result)
            }
            else {
                result(FlutterMethodNotImplemented)
            }
        })
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    // 位置情報取得ロジック
    private func getLocation(result: @escaping FlutterResult) {
        if CLLocationManager.authorizationStatus() == .authorizedWhenInUse || CLLocationManager.authorizationStatus() == .authorizedAlways {
            if let location = locationManager.location {
                result("\(location.coordinate.latitude),\(location.coordinate.longitude)")
            } else {
                result(FlutterError(code: "UNAVAILABLE", message: "Location not available.", details: nil))
            }
        } else {
            result(FlutterError(code: "PERMISSION_DENIED", message: "Location permission denied.", details: nil))
        }
    }
}

plist info.plist
        <key>NSLocationWhenInUseUsageDescription</key>
        <string>位置情報を使用します</string>
<!-- ↑ファイル末尾</dict>タグの上に-->

<Android>

MainActivity.kt
kotlin MainActivity.kt
package com.example.multios

import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
//import com.example.multios.map.GoogleMapViewFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel

/**
 * Android Studio内のOpen for Editing in Android Studioをクリックすることで、
 * もう一つAndroid Studio開きましょう。そうすることで補完が効くようになります。
 */
class MainActivity : FlutterActivity() {
    // 追加
    private val CHANNEL = "com.example.multios/location"

    // 2) 追加
//    var locationListener: LocationListener? = null

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        // 1) 追加
        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL
        ).setMethodCallHandler { call, result ->
            if (call.method == "getLocation") {
                val location = getLocation() // 実際の位置情報取得ロジックを実装
                if (location.isEmpty()) {
                    result.error("UNAVAILABLE", "Location not available.", null)
                } else {
                    result.success(location)
                }
            }
            // 2) 追加
            else {
                result.notImplemented()
            }
        }

        // 2) 追加
    }

    // 1) 位置情報取得ロジック
    private fun getLocation(): String {
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                1
            )
            return ""
        }

        val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
        val location: Location? = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
        return if (location == null) {
            ""
        } else {
            "${location.latitude},${location.longitude}"
        }
    }

// 2) 位置情報の更新を開始するロジックを実装
}


xml AndroidManifest.xml
<!-- <manifest xmlns:android="~">タグの↓に -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

EventChannel(一方向通信:Native -> Flutter)

Native側で発生したイベント(センサーデータの更新、位置情報の変更など)をFlutter側へ非同期で送信。
"バンドルID/~"のEventChannel文字列がFlutter側とNative側で一致している必要がある。

■用途
ネイティブの機能を常に監視する
このチャンネルは、特定のイベントやデータが更新された時に、それをFlutterアプリケーションでリッスンし、ハンドリングする

■位置情報を監視する実装例

<Flutter>

gps_loation_repository.dart

import 'dart:async';

import 'package:flutter/services.dart';
import './location_repository.dart';

/// GPSを使った位置情報を取得するための仕組み。
class GpsLocationRepository extends LocationRepository {
  static const platform = MethodChannel('com.example.multios/location');

  // 2) EventChannelの追加
  static const eventChannel =
  EventChannel('com.example.multios/locationUpdates');
  StreamSubscription<dynamic>? _locationSubscription;
  StreamController<Location>? _locationStreamController;

  /// 1) MethodChannelを使って位置情報を取得します。
  @override
  Future<Location> get() async {
    final String result = await platform.invokeMethod('getLocation');
    final splitted = result.split(',');
    return Location(double.parse(splitted[0]), double.parse(splitted[1]));
  }

  /// 2) EventChannelを使って位置情報を監視します。
  @override
  Stream<Location> watch() {
    if (_locationSubscription != null) {
      return _locationStreamController!.stream;
    }
    _locationSubscription = eventChannel.receiveBroadcastStream().listen(
          (event) {
        final splitted = (event as String).split(',');
        final location =
        Location(double.parse(splitted[0]), double.parse(splitted[1]));
        print("更新された位置情報: 緯度 ${location.latitude}, 経度 ${location.longitude}");
        _locationStreamController!.add(location);
      },
      onError: (dynamic error) {
        print('Received error: ${error.message}');
      },
    );
    platform.invokeMethod('watchLocation');

    _locationStreamController = StreamController<Location>();
    return _locationStreamController!.stream;
  }
}


<iOS>

AppDelegate.swift
swift AppDelegate.swift

import UIKit
import Flutter
import CoreLocation

// Android Studio内のOpen iOS/macOS module in Xcodeをクリックすることで、
// もう一つXcode開きましょう。そうすることで補完が効くようになります。
// , CLLocationManagerDelegate を追加することを忘れずに。
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate, FlutterStreamHandler {
    // 追加
    private var locationChannel: FlutterMethodChannel?
    private var locationManager: CLLocationManager!
    
    // 2) 追加
    private var eventChannel: FlutterEventChannel?
    private var eventSink: FlutterEventSink?
    
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        // ここから追加
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        locationChannel = FlutterMethodChannel(name: "com.example.multios/location",
                                               binaryMessenger: controller.binaryMessenger)
        locationChannel?.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            if call.method == "getLocation" {
                self.getLocation(result: result)
            } else if call.method == "watchLocation" {// 2) 追加
                print("watchLocation")
                self.watchLocation()
            } else {
                result(FlutterMethodNotImplemented)
            }
        })
        // ここまで追加
        
        // 2) 追加
        eventChannel = FlutterEventChannel(name: "com.example.multios/locationUpdates", binaryMessenger: controller.binaryMessenger)
        eventChannel?.setStreamHandler(self)
        
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    // 位置情報取得ロジック
    private func getLocation(result: @escaping FlutterResult) {
        if CLLocationManager.authorizationStatus() == .authorizedWhenInUse || CLLocationManager.authorizationStatus() == .authorizedAlways {
            if let location = locationManager.location {
                result("\(location.coordinate.latitude),\(location.coordinate.longitude)")
            } else {
                result(FlutterError(code: "UNAVAILABLE", message: "Location not available.", details: nil))
            }
        } else {
            result(FlutterError(code: "PERMISSION_DENIED", message: "Location permission denied.", details: nil))
        }
    }
}

// 2) 追加:位置情報監視処理
extension AppDelegate {
    
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        locationManager.stopUpdatingLocation()
        return nil
    }
    
    private func watchLocation() {
        // 位置情報の更新を開始するロジックを実装
        locationManager.startUpdatingLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            eventSink?("\(location.coordinate.latitude),\(location.coordinate.longitude)")
        }
    }
}

plist info.plist
MethodChannelの実装例と同様の設定

<Android>

MainActivity.kt
kotlin MainActivity.kt

package com.example.multios

import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
//import com.example.multios.map.GoogleMapViewFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel

/**
 * Android Studio内のOpen for Editing in Android Studioをクリックすることで、
 * もう一つAndroid Studio開きましょう。そうすることで補完が効くようになります。
 */
class MainActivity : FlutterActivity() {
    // 追加
    private val CHANNEL = "com.example.multios/location"

    // 2) 追加
    var locationListener: LocationListener? = null

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        // 1) 追加
        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL
        ).setMethodCallHandler { call, result ->
            if (call.method == "getLocation") {
                val location = getLocation() // 実際の位置情報取得ロジックを実装
                if (location.isEmpty()) {
                    result.error("UNAVAILABLE", "Location not available.", null)
                } else {
                    result.success(location)
                }
                // 2) 追加
            } else if (call.method == "watchLocation") {
                if (watchLocation()) {
                    result.success(true)
                } else {
                    result.error("UNAVAILABLE", "Location not available.", null)
                }
            } else {
                result.notImplemented()
            }
        }

        // 2) 追加
        EventChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            "com.example.multios/locationUpdates"
        ).setStreamHandler(
            object : EventChannel.StreamHandler {

                override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
                    locationListener = LocationListener { location ->
                        events?.success("${location.latitude},${location.longitude}")
                    }
                }

                override fun onCancel(arguments: Any?) {
                    // 位置情報の更新を停止するロジックを実装
                }
            }
        )

        // 3) 追加
//        flutterEngine
//            .platformViewsController
//            .registry
//            .registerViewFactory("map", GoogleMapViewFactory(flutterEngine.dartExecutor.binaryMessenger))

    }

    // 1) 位置情報取得ロジック
    private fun getLocation(): String {
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                1
            )
            return ""
        }

        val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
        val location: Location? = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
        return if (location == null) {
            ""
        } else {
            "${location.latitude},${location.longitude}"
        }
    }

    // 2) 位置情報の更新を開始するロジックを実装
    private fun watchLocation(): Boolean {
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                1
            )
            return false
        }

        val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
        locationManager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER,
            10000,
            0f,
            locationListener!!
        )
        return true
    }
}

xml AndroidManifest.xml
MethodChannelの実装例と同様の設定

PlatformView

ネイティブの画面をFlutterアプリに投影する

■用途
地図(AndroidはGoogle Map、 iOSはApple標準地図)表示分けなど

<Flutter>

map_page.dart
dart map_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/locations/location_repository_provider.dart';
import '../../domain/locations/location.dart';
import '../../use_cases/locations/get_location_use_case.dart';
import '../../use_cases/locations/watch_location_use_case.dart';
import 'map_view.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("真逆地図アプリ"),
      ),
      // 1) 位置情報を一度だけ取得します。
      // body: const _Body1(),
      // 2) 位置情報を常に更新できるようにします。
      body: const _Body2(),
    );
  }
}

class _Body1 extends ConsumerWidget {
  const _Body1({
    super.key,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    /// 1) 位置情報を一度だけ取得します。
    final location = ref.watch(getLocationUseCaseProvider);
    return switch (location) {
      AsyncData(:final value) => _Main(
        location: value,
      ),
      AsyncError(:final error, :final stackTrace) => Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("読み込みエラー: $error"),
            ElevatedButton(//ユーザーが位置情報を許容した後のリトライボタン
              onPressed: () {
                ref.refresh(getLocationUseCaseProvider);
              },
              child: Text('再試行'),
            ),
          ],
        ),
      ),
      _ => const Center(
        child: CircularProgressIndicator(),
      ),
    };
  }
}

class _Main extends StatefulWidget {
  final Location location;

  const _Main({Key? key, required this.location}) : super(key: key);

  @override
  _MainState createState() => _MainState();
}

class _MainState extends State<_Main> {
  // 現在表示している位置(デフォルトは現在位置)
  late Location displayedLocation;
  bool isOpposite = false; // 裏側の位置を表示しているかどうか

  @override
  void initState() {
    super.initState();
    displayedLocation = widget.location; // 初期状態では現在位置を表示
  }

  void switchLocation(bool toOpposite) {
    setState(() {
      isOpposite = toOpposite;
      if (toOpposite) {
        // 裏側位置に切り替え
        // 緯度は-90度から+90度までの値を取ります。赤道が0度、北極が+90度、南極が-90度
        // 緯度は符号を反転させるのみで逆側になります。
        double oppositeLatitude = -widget.location.latitude;
        // 経度は-180度から+180度までの値を取ります。本初子午線 を0度として、東方向に180度、西方向に-180度
        // 経度を反転させる には、 180を加算します。ただし、その値が 180度 を超えた場合 360度を引きます。
        double oppositeLongitude = widget.location.longitude + 180;
        if (oppositeLongitude > 180) {
          oppositeLongitude -= 360;
        }
        displayedLocation = Location(oppositeLatitude, oppositeLongitude);
      } else {
        // 現在位置に戻す
        displayedLocation = widget.location;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    // 現在位置と裏側位置の表示切り替え
    String locationText = isOpposite ?
    "裏側緯度:${displayedLocation.latitude.toStringAsFixed(2)} 裏側経度:${displayedLocation.longitude.toStringAsFixed(2)}" :
    "現在緯度:${widget.location.latitude.toStringAsFixed(2)} 現在経度:${widget.location.longitude.toStringAsFixed(2)}";

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(locationText),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: ElevatedButton(
                onPressed: () => switchLocation(!isOpposite),
                child: Text(isOpposite ? '現在の位置' : '裏側の位置'),
              ),
            ),
          ],
        ),
        Expanded(
          child: MapView(
            key: ValueKey(displayedLocation), // 表示位置が変わるたびにウィジェットを再構築
            latitude: displayedLocation.latitude,
            longitude: displayedLocation.longitude,
          ),
        ),
      ],
    );
  }
}


class _Body2 extends ConsumerWidget {
  const _Body2({
    super.key,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    /// 2) 位置情報を常に更新できるようにします。
    final location = ref.watch(watchLocationUseCaseProvider);



    return switch (location) {
      AsyncData(:final value) => _Main(
        location: value,
      ),
      AsyncError(:final error, :final stackTrace) => Center(
        // child: Text("読み込みエラー: $error"),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("読み込みエラー: $error"),
            ElevatedButton(//ユーザーが位置情報を許容した後のリトライボタン
              onPressed: () {
                ref.refresh(getLocationUseCaseProvider);
              },
              child: Text('再試行'),
            ),
          ],
        ),
      ),
      _ => const Center(
        child: CircularProgressIndicator(),
      ),
    };
  }
}


<iOS>

AppDelegate.swift
swift AppDelegate.swift

import UIKit
import Flutter
import CoreLocation

// Android Studio内のOpen iOS/macOS module in Xcodeをクリックすることで、
// もう一つXcode開きましょう。そうすることで補完が効くようになります。
// , CLLocationManagerDelegate を追加することを忘れずに。
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate, FlutterStreamHandler {
    // 1) 追加
    private var locationChannel: FlutterMethodChannel?
    private var locationManager: CLLocationManager!
    
    // 2) 追加
    private var eventChannel: FlutterEventChannel?
    private var eventSink: FlutterEventSink?
    
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        // ここから追加
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        locationChannel = FlutterMethodChannel(name: "com.example.multios/location",
                                               binaryMessenger: controller.binaryMessenger)
        locationChannel?.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            if call.method == "getLocation" {
                self.getLocation(result: result)
            } else if call.method == "watchLocation" {
                print("watchLocation")
                self.watchLocation()
            } else {
                result(FlutterMethodNotImplemented)
            }
        })
        // ここまで追加
        
        // 2) 追加
        eventChannel = FlutterEventChannel(name: "com.example.multios/locationUpdates", binaryMessenger: controller.binaryMessenger)
        eventChannel?.setStreamHandler(self)
        
        // 3) 追加
        let registrar = self.registrar(forPlugin: "multios")
        let factory = FlutterMapKitFactory(messenger: (registrar!.messenger()))
        self.registrar(forPlugin: "<multios>")!.register(
            factory,
            withId: "map")
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    // 位置情報取得ロジック
    private func getLocation(result: @escaping FlutterResult) {
        if CLLocationManager.authorizationStatus() == .authorizedWhenInUse || CLLocationManager.authorizationStatus() == .authorizedAlways {
            if let location = locationManager.location {
                result("\(location.coordinate.latitude),\(location.coordinate.longitude)")
            } else {
                result(FlutterError(code: "UNAVAILABLE", message: "Location not available.", details: nil))
            }
        } else {
            result(FlutterError(code: "PERMISSION_DENIED", message: "Location permission denied.", details: nil))
        }
    }
}

// 2) 追加:位置情報監視処理
extension AppDelegate {
    
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        locationManager.stopUpdatingLocation()
        return nil
    }
    
    private func watchLocation() {
        // 位置情報の更新を開始するロジックを実装
        locationManager.startUpdatingLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            eventSink?("\(location.coordinate.latitude),\(location.coordinate.longitude)")
        }
    }
}

plist info.plist
MethodChannelの実装例と同様の設定

<Android>

MainActivity.kt
map/GoogleMapView.kt
map/GoogleMapViewFactory.kt
gradle android/app/build.gradle
// 末尾に↓追加
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.google.android.gms:play-services-maps:18.2.0'
}
xml AndroidManifest.xml

MethodChannelの実装例と同様の設定に加えて↓

<!-- <application タグの↓に -->
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="Google MapのAPIキーを入れる"/>

Google MapのAPIキー取得は以下参照
https://developers.google.com/maps/documentation/android-sdk/get-api-key?hl=ja

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?