diff --git a/android/app/src/main/kotlin/com/example/flutter_call_native/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_call_native/MainActivity.kt index 6f57c69..e25159f 100644 --- a/android/app/src/main/kotlin/com/example/flutter_call_native/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/flutter_call_native/MainActivity.kt @@ -1,6 +1,31 @@ package com.example.flutter_call_native import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.* class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + // Creates a MethodChannel as soon as the FlutterEngine is attached to + // the Activity, and registers a MethodCallHandler. The Method.setMethodCallHandler + // is responsible to register a MethodCallHandler to handle the incoming calls. + + // The call parameter of MethodCallHandler has information about the incoming call, + // like method name, and arguments. The result parameter of MethodCallHandler is + // responsible to send the results of the call. + MethodChannel(flutterEngine.dartExecutor, "methodChannelDemo") + .setMethodCallHandler { call, result -> + val count: Int? = call.argument("count") + + if (count == null) { + result.error("INVALID ARGUMENT", "Value of count cannot be null", null) + } else { + when (call.method) { + "increment" -> result.success(count + 1) + "decrement" -> result.success(count - 1) + else -> result.notImplemented() + } + } + } + } } diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..e4aa716 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -7,6 +7,25 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + let flutterViewController = window.rootViewController as! FlutterViewController + FlutterMethodChannel(name: "methodChannelDemo", binaryMessenger: flutterViewController.binaryMessenger).setMethodCallHandler({ + (call: FlutterMethodCall, result: FlutterResult) -> Void in + + guard let count = (call.arguments as? NSDictionary)?["count"] as? Int else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Value of count cannot be null", details: nil)) + return + } + + switch call.method { + case "increment": + result(count + 1) + case "decrement": + result(count - 1) + default: + result(FlutterMethodNotImplemented) + } + }) + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/lib/counter_method_channel.dart b/lib/counter_method_channel.dart new file mode 100644 index 0000000..fe945b1 --- /dev/null +++ b/lib/counter_method_channel.dart @@ -0,0 +1,29 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +/// This class includes implementation of two platform methods [increment], +/// and [decrement] which are used to increment and decrement value +/// of count respectively. +class Counter { + /// Creates a [MethodChannel] with the specified name to invoke platform method. + /// In order to communicate across platforms, the name of MethodChannel + /// should be same on native and dart side. + static MethodChannel methodChannel = const MethodChannel('methodChannelDemo'); + + /// This method is responsible to increment and return the value of count. + static Future increment({required int counterValue}) async { + final result = await methodChannel + .invokeMethod('increment', {'count': counterValue}); + return result!; + } + + /// This method is responsible to decrement and return the value of count. + static Future decrement({required int counterValue}) async { + final result = await methodChannel + .invokeMethod('decrement', {'count': counterValue}); + return result!; + } +} diff --git a/lib/main.dart b/lib/main.dart index 202509b..f03e194 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import './method_channel_demo.dart'; void main() { runApp(const MyApp()); @@ -11,6 +12,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + routes: { + '/methodChannelDemo': (context) => const MethodChannelDemo(), + }, title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. @@ -24,13 +28,29 @@ class MyApp extends StatelessWidget { // is not restarted. primarySwatch: Colors.blue, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: const MyHomePage(), ); } } -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); +class DemoInfo { + final String demoTitle; + final String demoRoute; + + DemoInfo(this.demoTitle, this.demoRoute); +} + + +List demoList = [ + DemoInfo( + 'MethodChannel Demo', + '/methodChannelDemo', + ), +]; + +class MyHomePage extends StatelessWidget { + + const MyHomePage({super.key}); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect @@ -41,75 +61,32 @@ class MyHomePage extends StatefulWidget { // used by the build method of the State. Fields in a Widget subclass are // always marked "final". - final String title; - @override - State createState() => _MyHomePageState(); + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Platform Channel Sample'), + ), + body: ListView( + children: demoList.map((demoInfo) => DemoTile(demoInfo)).toList(), + ), + ); + } } -class _MyHomePageState extends State { - int _counter = 0; +/// This widget is responsible for displaying the [ListTile] on [HomePage]. +class DemoTile extends StatelessWidget { + final DemoInfo demoInfo; - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } + const DemoTile(this.demoInfo, {super.key}); @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + return ListTile( + title: Text(demoInfo.demoTitle), + onTap: () { + Navigator.pushNamed(context, demoInfo.demoRoute); + }, ); } } diff --git a/lib/method_channel_demo.dart b/lib/method_channel_demo.dart new file mode 100644 index 0000000..42b65ec --- /dev/null +++ b/lib/method_channel_demo.dart @@ -0,0 +1,90 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import './counter_method_channel.dart'; + +/// The widget demonstrates how to use [MethodChannel] to invoke platform methods. +/// It has two [ElevatedButton]s to increment and decrement the value of +/// [count], and a [Text] widget to display its value. +class MethodChannelDemo extends StatefulWidget { + const MethodChannelDemo({super.key}); + + @override + State createState() => _MethodChannelDemoState(); +} + +class _MethodChannelDemoState extends State { + int count = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('MethodChannel Demo'), + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Value of count is $count', + style: Theme.of(context).textTheme.headline5, + ), + const SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Whenever users press the ElevatedButton, it invokes + // Counter.increment method to increment the value of count. + ElevatedButton.icon( + onPressed: () async { + try { + final value = await Counter.increment(counterValue: count); + setState(() => count = value); + } catch (error) { + showErrorMessage( + context, + (error as PlatformException).message!, + ); + } + }, + icon: const Icon(Icons.add), + label: const Text('Increment'), + ), + + // Whenever users press the ElevatedButton, it invokes + // Counter.decrement method to decrement the value of count. + ElevatedButton.icon( + onPressed: () async { + try { + final value = await Counter.decrement(counterValue: count); + setState(() => count = value); + } catch (error) { + showErrorMessage( + context, + (error as PlatformException).message!, + ); + } + }, + icon: const Icon(Icons.remove), + label: const Text('Decrement'), + ) + ], + ) + ], + ), + ); + } + + void showErrorMessage(BuildContext context, String errorMessage) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(errorMessage), + ), + ); + } +}