Flutter-A glimpse of flutter

1、Official website

We can get numerous knowledge about flutter at official website.
The basic skeleton of flutter is below:

image

We can find out that it can be apply to Android and iOS platform,and package Android material design for Android developers.
What we care about is how we can develop application ignore platform difference,fortunately,google has taken this into account.

2、Platform invoke

Channel deliver a way to communicate with platform plugins using asynchronous method calls.
Including MethodChannel,EventChannel,BasicMessageChannel.

(1)MethodChannel

MethodChannel provide a method for flutter to invoke Android method,Android kotlin codes:

MethodChannel(flutterView, CHANNEL).setMethodCallHandler { p0, p1 ->
    val method = p0!!.method
    MyLog.d(TAG, method)
    if (method == "showToast") {
        if (p0!!.hasArgument("msg") && !TextUtils.isEmpty(p0!!.argument<String>("msg") as String)) {
            context.toast(p0!!.argument<String>("msg") as String)
        } else {
            context.toast("toast text must not null")
        }
    } else if (method == "getData") {
        if(Looper.getMainLooper() == Looper.myLooper()){
            MyLog.d(TAG, "in main thread")
        }
        val type = p0!!.argument<Int>("type") as Int
        MyLog.d(TAG, "getData type:$type")
        if (type == GetDataManager.TYPE_YEAR) {
            if (!yearlyDataList.isEmpty()) {
                yearlyDataList.clear()
            }
        }
        getDataManager.getData(curTime, type)
    } else if (method == "getAppIcon") {
        val pkgName = p0!!.argument<String>("pkgName") as String
        val pkgIcon = SystemDataUtil.getAppIcon(this, pkgName)
        p1.success(pkgIcon)
    } else if (method == "getAppIconList") {
        val pkgNameList = p0!!.argument<List<String>>("pkgNameList") as List<String>
        if (pkgNameList != null && pkgNameList.isNotEmpty()) {
            val pkgIconList = mutableListOf<String>()
            pkgNameList.forEach {
                val pkgIcon = SystemDataUtil.getAppIcon(this, it)
                pkgIconList.add(pkgIcon!!)
            }
            p1.success(pkgIconList)
        }
    }
}

The codes define in onCreate method,and can not locate somewhere else,because methodchannel is unsafe thread.In addition,if we wanna return data from native to flutter,invoke:

p1.success(data)

And flutter invoke above codes with a name:

static const platform =
  const MethodChannel("com.example.test/mainActivity");

Initializing a methodchannel object first:

Future<void> showToast() async {
    try {
      await platform.invokeMethod("showToast", {"msg": "Toast"});
    } on PlatformException catch (e) {
      print(e.toString());
    }
}

Future<void> getData(int type) async {
    try {
      await platform.invokeMethod("getData", {"type": type});
    } on PlatformException catch (e) {
      print(e.toString());
    }
}

Future<String> getAppIcon(String pkgName) async {
    Future<String> result;
    try {
      print("getAppIcon $pkgName");
      result = await platform.invokeMethod("getAppIcon", {"pkgName": pkgName});
    } on PlatformException catch (e) {
      print(e.toString());
    }
    return result;
}

Future<List<String>> getAppIconList(List<AppUsagePercent> tempAppUsagePercentList) async {
    if(tempAppUsagePercentList == null){
      return null;
    }
    Future<List<String>> result;
    List<String> pkgNames = [];
    for (AppUsagePercent appUsagePercent in tempAppUsagePercentList) {
      pkgNames.add(appUsagePercent.pkgName);
    }
    try {
      var data = await platform.invokeMethod("getAppIconList", {"pkgNameList": pkgNames});
      print("get app icon data finished");
    } on Exception catch (e) {
      print(e.toString());
    }
    return result;
}

(2)EventChannel

EventChannel can deliver native message to flutter,such as broadcast or something else.
Defining the listen method in flutter first:

static const EventChannel eventChannel =
  const EventChannel("com.example.test/typeData");

And then:

eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
void addListener(InteractListener listener) {
   if (listener == null) {
     return;
   }
   listenerList.add(listener);
}

void _onEvent(Object event) {
   print("_onEvent is invoke$event");
   for (InteractListener listener in listenerList) {
     listener.onEvent(event);
   }
}

void _onError(Object error) {
   for (InteractListener listener in listenerList) {
     listener.onError(error);
   }
}

What's happened in native platform:

lateinit var listenEvents: EventChannel.EventSink

init a EventChannel.EventSink object to operate data.
Then register the event stream handler:

EventChannel(flutterView, DATA_RESULT_CHANNEL).setStreamHandler(object : EventChannel.StreamHandler {
    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        listenEvents = events!!
    }

    override fun onCancel(arguments: Any?) {
    }
})

At last,we can throw data from native to flutter:

@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun receiveYearlyData(usageDetailForEventBusBean: UsageDetailForEventBusBean) {
    synchronized(GetDataManager::class.java) {
        MyLog.d(TAG, "receiveYearlyData:${usageDetailForEventBusBean.usageDetailBean}")
        if (usageDetailForEventBusBean.isFinish) {
            listenEvents!!.success(JsonUtil.toJson(yearlyDataList))
            return
        }
        yearlyDataList.add(usageDetailForEventBusBean.usageDetailBean!!)
    }
}

(3)BasicMessageChannel

BasicMessageChannel is similar to Method channel,The Dart type of messages sent and received is T, but only the values supported by the specified MessageCodec can be used,the difference is T data type,which supports basic, asynchronous message passing using a custom message codec. Further, you can use the specialized BinaryCodec, StringCodec, and JSONMessageCodec classes, or create your own codec.
The following table shows how Dart values are received on the platform side and vice versa:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

This website can get some knowledge about platform channel.
And alibaba's xianyu tech team published some articles in yuque,the url is https://www.yuque.com/xytech/flutter/.

3、Widget

Widget includes StatefulWidget and StatelessWidget class.The lifecycle below:

  • createState()
  • mounted == true
  • initState()
  • didChangeDependencies()
  • build()
  • didUpdateWidget()
  • setState()
  • deactivate()
  • dispose()
  • mounted == false

(1)StatefulWidget

StatefulWidget includes createState method which init State class,State class can update widgets by setState method,and it contains initState and build method.

class TimeView extends StatefulWidget {
  @override
  _TimeViewState createState() => _TimeViewState();
}

class _TimeViewState extends State<TimeView> implements InteractListener {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(child:Text(""));
  }
}

(2)StatelessWidget

The build method of a stateless widget is typically only called in three situations: the first time the widget is inserted in the tree, when the widget's parent changes its configuration, and when an InheritedWidget it depends on changes.

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Container(child:Text(""));
  }
}

(3)Push or Pop

We can find some useful information here:https://flutter.io/docs/cookbook/navigation/navigation-basics.Navigator is the bridge between two widgets while navigating.

  • 🐫Common mode
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation Basics',
    home: FirstScreen(),
  ));
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Launch screen'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondScreen()),
            );
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Screen"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}
  • 🐫Named for Route
MaterialApp(
  // Start the app with the "/" named route. In our case, the app will start
  // on the FirstScreen Widget
  initialRoute: '/',
  routes: {
    // When we navigate to the "/" route, build the FirstScreen Widget
    '/': (context) => FirstScreen(),
    // When we navigate to the "/second" route, build the SecondScreen Widget
    '/second': (context) => SecondScreen(),
  },
);

When widget give a name to each routes,we must make sure that it does not contain home argument.And On pressing it will guide to second widget:

// Within the `FirstScreen` Widget
onPressed: () {
  // Navigate to the second screen using a named route
  Navigator.pushNamed(context, '/second');
}

Returning from second widget:

// Within the SecondScreen Widget
onPressed: () {
  // Navigate back to the first screen by popping the current route
  // off the stack
  Navigator.pop(context);
}
  • 🐫 Returned with data

Directly with codes:

class SelectionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: Text('Pick an option, any option!'),
    );
  }

  // A method that launches the SelectionScreen and awaits the result from
  // Navigator.pop
  _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that will complete after we call
    // Navigator.pop on the Selection Screen!
    final result = await Navigator.push(
      context,
      // We'll create the SelectionScreen in the next step!
      MaterialPageRoute(builder: (context) => SelectionScreen()),
    );
  }
}

In second widget:

class SelectionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pick an option'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // Pop here with "Yep"...
                  Navigator.pop(context, 'Yep!');
                },
                child: Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // Pop here with "Nope"
                  Navigator.pop(context, 'Nope!');
                },
                child: Text('Nope.'),
              ),
            )
          ],
        ),
      ),
    );
  }
}
  • 🐫 Pushed with data

The pushed-widget should contain constructor with arguments which can be pushed,for instance:

Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => DetailScreen(todo: todos[index]),
    ),
);

The second widget:

class DetailScreen extends StatelessWidget {
  // Declare a field that holds the Todo
  final Todo todo;

  // In the constructor, require a Todo
  DetailScreen({Key key, @required this.todo}) : super(key: key);
}

Article will be synced to wechat blog:Android部落格

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容