1. 概要
今回は下記記事を参考にNativeメソッドを呼び出し、連携してみます。
Androidデバイスのバッテリー残量を取得し、画面に表示します。
- Nativeコードは、上記参考ページのKotlinをそのまま流用します。
- Flutterコードは、上記参考ページを少し改良してます。
- 状態管理
- バッテリーのアイコンを表示
- 残量に合わせてアイコンを切り替える
- Nativeとの通信はRepositoryで行う
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. FlutterとNativeの連携方法
FlutterでNativeと連携する方法は、下記3つがあるようです。
- MethodChannel
- プラットフォーム(Android/iOS等)側のメソッドをFlutter側より呼び出す事ができる。
- https://api.flutter.dev/flutter/services/MethodChannel-class.html
- EventChannel
- プラットフォーム(Android/iOS等)側からFlutter側に通知する事ができる。
- https://api.flutter.dev/flutter/services/EventChannel-class.html
- MessageChannel
- プラットフォーム (Android/iOS等)側 とFlutter側間で双方向でメッセージを送受信する事ができる。
- https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html
今回は「MethodChannel」を使い、やってみます。
※下記図は公式より引用
3. プロジェクトの準備
3-1. プロジェクトを作成
3-2. Dependencyのインストール
flutter pub add flutter_riverpod
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_riverpod: ^1.0.4
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
4. ソースコード(Kotlin)
4-1. android/app/src/main/kotlin/com/example/batterylevel/MainActivity.kt
- このファイルはFlutterプロジェクトを作成すると、自動生成されるので内容を変更
package com.example.batterylevel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// This method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
Androidデバイスのバッテリー残量を取得し、返す。
- 「CHANNEL」について
- samples.flutter.dev/battery
- Flutter側との通路
The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique ‘domain prefix’, for example:
Writing custom platform-specific code | Fluttersamples.flutter.dev/battery
.
- 「getBatteryLevel」について
- Flutter側で呼び出すメソッド名
5. ソースコード(Dart)
5-1. lib/main.dart
import 'package:batterylevel/models/battery_data.dart';
import 'package:batterylevel/views/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'notifiers/battery_notifier.dart';
final batteryProvider =
StateNotifierProvider<BatteryNotifier, BatteryData>((ref) {
return BatteryNotifier();
});
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
5-2. lib/views/home.dart
import 'package:batterylevel/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'components/battery_icon.dart';
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key, required String title}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(batteryProvider);
final batteryNotifier = ref.read(batteryProvider.notifier);
BatteryIcon batteryIcon = BatteryIcon(amount: data.amount);
return Scaffold(
appBar: AppBar(
title: const Text('Battery Level'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => batteryNotifier.getBatteryLevel(),
child: const Text('Get Battery Level')),
batteryIcon.batteryIcon(),
Text(_getBatteryLevel(data.amount))
],
),
),
);
}
String _getBatteryLevel(int amount) {
return 'Battery level at $amount % .';
}
}
5-3. lib/views/components/battery_icon.dart
import 'package:flutter/material.dart';
class BatteryIcon {
final int amount;
const BatteryIcon({required this.amount});
Widget batteryIcon() {
Color batteryColor = Colors.grey;
IconData batteryIcon = Icons.battery_alert;
if (amount >= 20 && amount < 80) {
batteryColor = Colors.blue;
batteryIcon = Icons.battery_charging_full;
} else if (amount >= 80) {
batteryColor = Colors.green;
batteryIcon = Icons.battery_full;
}
return SizedBox(
width: 200,
height: 200,
child: Icon(
batteryIcon,
size: 200,
color: batteryColor,
),
);
}
}
5-4. lib/models/battery_data.dart
import 'package:flutter/material.dart';
@immutable
class BatteryData {
final int amount;
const BatteryData({required this.amount});
}
5-5. lib/repository/battery_repository.dart
import 'package:flutter/services.dart';
class BatteryRepository {
static const platform = MethodChannel('samples.flutter.dev/battery');
Future<int> getBatteryLevel() async {
int amount = 0;
try {
amount = await platform.invokeMethod('getBatteryLevel');
// ignore: empty_catches
} on PlatformException {}
return amount;
}
}
Native側のメソッドを呼び出す。
- 「MethodChannel」
- samples.flutter.dev/battery
- Native側との通路
- 「getBatteryLevel」
- Native側に用意されているメソッド名
5-6. lib/notifiers/battery_notifier.dart
import 'package:batterylevel/models/battery_data.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../repository/battery_repository.dart';
class BatteryNotifier extends StateNotifier<BatteryData> {
BatteryNotifier() : super(const BatteryData(amount: 0));
void getBatteryLevel() async {
BatteryRepository repository = BatteryRepository();
int amount = await repository.getBatteryLevel();
state = BatteryData(amount: amount);
}
}
6. 結果
6-1. 10% / 50% / 90%
- バッテリー残量に合わせてバッテリーアイコンと数値が変化
下記ページにて、Android側のNativeに引数を渡して処理する内容を書いたので合わせて読んで頂けますと幸いです。
下記ページにて、EventChannelで連携する内容を書いたので合わせて読んで頂けますと幸いです。
7. 参考
投稿者プロフィール
-
開発好きなシステムエンジニアです。
卓球にハマってます。