1、Official website
We can get numerous knowledge about flutter at official website.
The basic skeleton of flutter is below:
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("msg") as String)) {
context.toast(p0!!.argument("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("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("pkgName") as String
val pkgIcon = SystemDataUtil.getAppIcon(this, pkgName)
p1.success(pkgIcon)
} else if (method == "getAppIconList") {
val pkgNameList = p0!!.argument>("pkgNameList") as List
if (pkgNameList != null && pkgNameList.isNotEmpty()) {
val pkgIconList = mutableListOf()
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 showToast() async {
try {
await platform.invokeMethod("showToast", {"msg": "Toast"});
} on PlatformException catch (e) {
print(e.toString());
}
}
Future getData(int type) async {
try {
await platform.invokeMethod("getData", {"type": type});
} on PlatformException catch (e) {
print(e.toString());
}
}
Future getAppIcon(String pkgName) async {
Future result;
try {
print("getAppIcon $pkgName");
result = await platform.invokeMethod("getAppIcon", {"pkgName": pkgName});
} on PlatformException catch (e) {
print(e.toString());
}
return result;
}
Future> getAppIconList(List tempAppUsagePercentList) async {
if(tempAppUsagePercentList == null){
return null;
}
Future> result;
List 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 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: [
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部落格