Recently, I have been working on Flutter of the company’s new project, mainly responsible for writing part of Flutter pages and bridging with native Android. Due to the shortage of staff, the main integration work was entrusted to the students of the platform group. The company platform Group provides a complete set of integrated toolchains, development tools, MVVM structures and a series of wheels, right out of the box. For a long time, only stay in the use of the level, few in-depth study, or need to see more.
This time integrate Flutter for old projects and rewrite the post details page using Flutter. To experience the construction and development of hybrid mode provided by the official.
The old native page that needs to be rewritten this time is:
The rewritten page for Flutter is:
Project address: github.com/stevenwsg/X…
Well, without further ado, let’s get started.
1. Old projects integrate WITH Flutter
1.1 Flutter mixed development mode
Flutter hybrid development mode generally has two ways:
1. The native project is a subproject of the Flutter project. The default user of Flutter creates the Android and iOS project directories, which can be used for native client development.
Create a Flutter Module as a dependency and add it to an existing native project.
The second approach is more decoupled than the first approach, especially for existing projects with lower renovation costs.
1.2 Creating a Flutter Module
Create a Flutter Module using As
Select File->New->New Flutter Project in As and select Flutter Module to create a Flutter Module subproject As follows:
1.3 Two ways to add Flutter
There are two ways to add Flutter to a native project
- Aar integration into existing Android projects
- Integrate into existing Android projects as Flutet Modules
In the second way, the Flutter Module is integrated into an existing Android project and mixed compiled so that the Flutter thermal update can be used.
In Jenkins automatic packaging, the first method is adopted. The Flutter project is converted into an AAR product, and the Android APK file is compiled with the aar product generated.
Integrate into existing Android projects as Flutet Modules:
Configure the flutter module in the setting.gradle file as follows:
Include ':app', ':easeui' : setBinding(new Binding([gradle: this])) evaluate(new File(settingsDir, '.. /flutter_bbs/.android/include_flutter.groovy' ))Copy the code
Then add the dependencies of the flutter Module to the build.gradle file as follows:
dependencies {
implementation project(':flutter')
}
Copy the code
After the build has been completed, the project has become a mixed build of the native project and the Flutter project.
1.4 Adding a Page
This allows the jump from the native interface to the Flutter interface
Modify the Flutter entry file
import 'package:flutter/material.dart'; import 'package:flutter_bbs/post_deatil/view/post_deatil_page.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: PostDetailPage(), ); }}Copy the code
Display the Community details page for Flutter at this time
import 'package:flutter/material.dart'; class PostDetailPage extends StatefulWidget { @override State<StatefulWidget> createState() { return PostDetailState(); } } class PostDetailState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(" 信 息 "),), body: Center(child: Text(" 信 息 "), style: TextStyle(fontSize: 20, color: Colors.blueAccent), ), ), ); }}Copy the code
Create an Activity in the native project that inherits FlutterActivity and declares it in the androidmanifest.xml file:
class MomentDetailActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
Copy the code
<activity android:name=".flutter.MomentDetailActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|dens ity|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> </activity>Copy the code
How do I start this Activity?
startActivity(new Intent(getActivity(), MomentDetailActivity.class));
Copy the code
The effect is as follows:
2, rewrite the post details page
The page rewritten using Flutter is the post details page
As you can see, the entire page can be done with a single ListView, which contains multiple types. Post details, split lines, comments, comments, etc
2.1 Integrate the Bmob Flutter warehouse
Since the original project used Bmob cloud to provide data services, the Flutter project also needed to integrate Bmob warehouse to achieve data access and access addresses
Added dependencies to the Pubspec. yaml file of the Flutter project
Dependencies: data_plugin: ^ 0.0.16Copy the code
Enter the following command on the terminal to install:
flutter packages get
Copy the code
Perform the following initialization operations in runApp:
/ * * * the encryption initialization. * / Bmob init (" https://api2.bmob.cn ", "appId", "apiKey");Copy the code
2.2 The native project page passes the post Id to the Flutter page
In native projects, the way to jump to a Flutter page was changed to:
val intent = Intent(context, MomentDetailActivity::class.java) intent.action = Intent.ACTION_RUN intent.putExtra( "route", "moment? noteId = ${note.objectId}" ) context? .startActivity(intent)Copy the code
In the Flutter project, receive the parameters passed:
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
Routes.MOMENT: (BuildContext context) => PostDetailPage(null),
},
onGenerateRoute: (settings) {
Uri uri = Uri.parse(settings.name);
Map<String, String> params = uri.queryParameters;
return MaterialPageRoute(
builder: (context) => PostDetailPage(params));
});
Copy the code
Now the Flutter post details page gets the post Id
2.3 Flutter obtains post information based on the post Id
2.3.1 Data pull
Create a network information class
import 'package:data_plugin/bmob/bmob_query.dart'; import 'package:data_plugin/utils/dialog_util.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bbs/post_deatil/model/bean/note.dart'; class NetWorkRepo { static Note getNoteInfo(BuildContext context, String noteId) { BmobQuery<Note> query = BmobQuery(); query .queryObject(noteId) .then((value) => {showSuccess(context, value.toString())}); }}Copy the code
Pull when the PostDetailPage is initialized
class PostDetailState extends State<PostDetailPage> { String _noteId; @override void initState() { super.initState(); _noteId = widget._map["noteId"] as String; _initData(); } void _initData() {NetWorkRepo. GetNoteInfo (context, _noteId); }... }Copy the code
The result of the pull is zero
2.3.2 Json parsing
Deserialize Json data into bean entities.
The Json2Dart plugin is used here (I think the jSON_serializable library is difficult to use and has many holes).
The generated code is:
import 'package:data_plugin/bmob/table/bmob_object.dart'; class Note extends BmobObject { String content; String createdAt; String objectId; int replaycount; String title; int top; String typeid; String updatedAt; String userid; int zancount; Note.fromJsonMap(Map<String, dynamic> map) : content = map["content"], createdAt = map["createdAt"], objectId = map["objectId"], replaycount = map["replaycount"], title = map["title"], top = map["top"], typeid = map["typeid"], updatedAt = map["updatedAt"], userid = map["userid"], zancount = map["zancount"]; Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['content'] = content; data['createdAt'] = createdAt; data['objectId'] = objectId; data['replaycount'] = replaycount; data['title'] = title; data['top'] = top; data['typeid'] = typeid; data['updatedAt'] = updatedAt; data['userid'] = userid; data['zancount'] = zancount; return data; } @override Map getParams() { toJson(); } @override String toString() { return 'Note{content: $content, createdAt: $createdAt, objectId: $objectId, replaycount: $replaycount, title: $title, top: $top, typeid: $typeid, updatedAt: $updatedAt, userid: $userid, zancount: $zancount}'; }}Copy the code
At this point, the Json data has been converted to Bean entities:
2.3.3 the UI display
Next, the post is physically displayed on the UI
Dart: Post_deatil_page. Dart: post_deatil_page. Dart: Post_deatil_page
@override Widget build(BuildContext context) {return Scaffold(appBar: appBar (title: Text(" post details "),), body: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return _buildListViewCell( items[index]); // Build different widgets to populate the ListView based on the data},); } Widget _buildListViewCell(Object Object) {if (Object is Note) {return MomentDetailWidget(Object); // Return post details Widget}}Copy the code
Post details MomentDetailWidget
import 'package:flutter/material.dart'; import 'package:flutter_bbs/post_deatil/model/bean/note.dart'; class MomentDetailWidget extends StatelessWidget { final Note note; MomentDetailWidget(this.note); @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(left: 20, top: 10, bottom: 10, right: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ _buildHeaderWidget(), _buildContentWidget(), _buildIconWidget(), _buildReplayWidget(), ], ), ); } Widget _buildHeaderWidget() { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( margin: EdgeInsets.only(right: 10), child: ClipOval( child: Image.asset( "images/logo.webp", width: 80, height: 80, ), ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( note.title ?? "", style: TextStyle(color: Colors.black54, fontSize: 20), maxLines: 1, overflow: TextOverflow.ellipsis, ), Container( margin: EdgeInsets.only(top: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( note.typeid ?? "", style: TextStyle(color: Colors.black45, fontSize: 16), ), Expanded(child: Container()), Text( note.updatedAt?.substring(0, 10) ?? "", style: TextStyle(color: Colors.black45, fontSize: 16), ) ], ), ), ], )), ], ); } Widget _buildContentWidget() { return Container( margin: EdgeInsets.only(top: 10), child: Expanded( child: Text( note.content ?? "", style: TextStyle(color: Colors.black54, fontSize: 16), ), ), ); } Widget _buildIconWidget() { return Container( margin: EdgeInsets.only(top: 20), child: Flex( direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row( children: [ Image.asset( "images/zan.webp", width: 20, height: 20, ), Container( margin: EdgeInsets.only(left: 5), child: Text( note.zancount?.toString() ?? "", style: TextStyle(fontSize: 14), ), ) ], ), Row( children: [ Image.asset( "images/replay.webp", width: 20, height: 20, ), Container( margin: EdgeInsets.only(left: 5), child: Text( note.replaycount?.toString() ?? "", style: TextStyle(fontSize: 14), ), ) ], ) ], ), ); } Widget _buildReplayWidget() { return Container( margin: EdgeInsets.only( top: 20, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: TextField( decoration: InputDecoration( hintText: HintStyle: TextStyle(fontFamily: 'MaterialIcons', fontSize: 16), contentPadding: EdgeInsets. Only (top: 8, bottom: 8), border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(5)), ), filled: true, ), )), Container( margin: EdgeInsets.only(left: 20), child: OutlinedButton (onPressed: () {}, child, Text (" comment "),),),),); }}Copy the code
The results are as follows:
2.4 Flutter obtains comment information based on the post Id
2.4.1 Data Acquisition
Static List<Comment> getCommentInfo(BuildContext context, String noteId) { BmobQuery<Comment> query = BmobQuery(); query.addWhereEqualTo("noteid", noteId); query.queryObjects().then((value) { List<Comment> list = List(); value.forEach((element) { list.add(Comment.fromJsonMap(element)); }); print(list.toString()); }).catchError((e) { showError(context, BmobError.convert(e).error); }); }Copy the code
The request result is:
2.4.2 Json parsing
import 'package:data_plugin/bmob/table/bmob_object.dart'; class Comment extends BmobObject { String content; String createdAt; String noteid; String objectId; String updatedAt; String userid; String username; Comment.fromJsonMap(Map<String, dynamic> map) : content = map["content"], createdAt = map["createdAt"], noteid = map["noteid"], objectId = map["objectId"], updatedAt = map["updatedAt"], userid = map["userid"], username = map["username"]; Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['content'] = content; data['createdAt'] = createdAt; data['noteid'] = noteid; data['objectId'] = objectId; data['updatedAt'] = updatedAt; data['userid'] = userid; data['username'] = username; return data; } @override Map getParams() { toJson(); } @override String toString() { return 'Comment{content: $content, createdAt: $createdAt, noteid: $noteid, objectId: $objectId, updatedAt: $updatedAt, userid: $userid, username: $username}'; }}Copy the code
The analytical results are:
2.4.3 UI display
The details page of the ListView is transformed into a multi-type ListView, can display posts, lines, comments and other content
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: BackButton(onPressed: () {}), title: Text(" itemCount: items.length "), centerTitle: true,), Body: ListView.Builder (itemCount: items.length, itemBuilder: (context, index) { return _buildListViewCell( items[index]); // Build different widgets to populate the ListView based on the data},); } Widget _buildListViewCell(Object object) { if (object is Note) { return MomentDetailWidget(object); } else if (object is Comment) {return CommentDetailWidget(object); } else if (object is DividerBean) {return DividerWidget(); } else if (object is CommentEmptyBean) {return CommentEmptyWidget(); Else if (object is CommentTitleBean) {return CommentTitleWidget(object.mentnum);} else if (object is CommentTitleBean) {return CommentTitleWidget(object.mentnum); } else {return Container(); Container}}Copy the code
Comment on the Widget
import 'package:flutter/material.dart'; import 'package:flutter_bbs/post_deatil/model/bean/comment.dart'; class CommentDetailWidget extends StatelessWidget { final Comment comment; CommentDetailWidget(this.comment); @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(left: 20, top: 8, bottom: 8, right: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text( comment.username + " : ", style: TextStyle(fontSize: 16, color: Colors.blue), ), Expanded( child: Text( comment.content, style: TextStyle(fontSize: 16), maxLines: 1, overflow: TextOverflow.ellipsis, )), ], )); }}Copy the code
The result is as follows:
2.5 Making comments
Because the commenting data structure needs to be populated with its own userId and userName, Flutter is implemented to get the user’s own UID and userName information natively.
2.5.1 Using MethodChannel to get a user’s UID natively
Flutter part
import 'package:flutter/services.dart'; class MomentBridge { static const String BRIDGE_NAME = "flutter.bbs/moment"; static const String METHOD_GET_USER_INFO = "getUserInfo"; static const String KEY_USER_ID = "key_user_id"; static const String KEY_USER_NAME = "key_user_name"; static const _methodChannel = const MethodChannel(BRIDGE_NAME); static Future<Map> getUserInfo() async { try { Map res = await _methodChannel.invokeMethod(METHOD_GET_USER_INFO); print("getUserInfo suc" + res.toString()); return res; } catch (e) { print("getUserInfo error" + e.toString()); } return Map(); }}Copy the code
Native Android:
package com.wsg.xsybbs.flutter import android.os.Bundle import cn.bmob.v3.BmobUser import com.wsg.xsybbs.bean.User import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant /** * Create by wangshengguo on 2021/3/25. */ class MomentDetailActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // flutterEngine.let { // GeneratedPluginRegistrant. RegisterWith (it) / /} / / registered MethodChannel MethodChannel (flutterEngine dartExecutor, MomentBridge.BRIDGE_NAME ).setMethodCallHandler { call, result -> when (call.method) { MomentBridge.METHOD_GET_USER_INFO -> { val user = BmobUser.getCurrentUser(User::class.java) val map: HashMap<String, String> = hashMapOf() map[MomentBridge.KEY_USER_ID] = user.objectId map[MomentBridge.KEY_USER_NAME] = user.username result.success(map) } else -> { } } } } }Copy the code
After the call is initiated, the result is displayed
2.5.2 Comment
Static void addComment(BuildContext Context, String noteId, String content, Function(Comment comment) update) async { Comment comment = Comment(); comment.noteid = noteId; comment.content = content; Map map = await MomentBridge.getUserInfo(); comment.userid = map[MomentBridge.KEY_USER_ID]; comment.username = map[MomentBridge.KEY_USER_NAME]; Comment.save ().then((value) {toast.show (" comment on success ", context); update(comment); }).catchError((e) { showError(context, BmobError.convert(e).error); }); }Copy the code
2.5.3 refresh the UI
When a comment is successfully posted, insert it to the last item in the comment list
return MomentDetailWidget(object, (String content) { NetWorkRepo.addComment(context, _noteId, content, (comment) {setState(() {if (items[items.length-1] is CommentEmptyBean) {// If the comment list is empty, remove the UI when the comment is empty. Insert comments into the data set to show kitems.removeat (Kitems.leng-1); items.add(comment); } else {// Insert items. Add (comment); }}); }); });Copy the code
In the future, I will continue to improve the function of liking and rewrite the page using MVVM if I have time.
3. Project address
The project address is: github.com/stevenwsg/X…
4, reference
The Android hybrid Development chapter of the Flutter series
Native jump Flutter transfer parameters
bmob-flutter-sdk
Dart (*) JSON serialization
toast