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:

We can find out that it can be applied 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:

DartAndroidiOS
nullnullnil (NSNull when nested)
booljava.lang.BooleanNSNumber numberWithBool:
intjava.lang.IntegerNSNumber numberWithInt:
int, if 32 bits not enoughjava.lang.LongNSNumber numberWithLong:
doublejava.lang.DoubleNSNumber numberWithDouble:
Stringjava.lang.StringNSString
Uint8Listbyte[]FlutterStandardTypedData typedDataWithBytes:
Int32Listint[]FlutterStandardTypedData typedDataWithInt32:
Int64Listlong[]FlutterStandardTypedData typedDataWithInt64:
Float64Listdouble[]FlutterStandardTypedData typedDataWithFloat64:
Listjava.util.ArrayListNSArray
Mapjava.util.HashMapNSDictionary

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部落格

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值