I recorded in a Flutter based on open source network in China client requests and data storage, this record is the use of plug-in in the app, because a lot of features is not built into the Flutter, so we need to introduce some plugins to help us to complete certain functions, such as page load within the app, gallery choose photos, etc.

The index The article
1 Write an open Source Chinese Client based on Flutter from 0 (1)

Flutter build | nuggets technical essay introduction and development environment
2 Write an open Source Chinese Client based on Flutter from 0 (2)

Dart Grammar Basics
3 Write an open Source Chinese Client based on Flutter from 0 (3)

Introduction to Flutter & Common Widgets
4 Write an open Source Chinese Client based on Flutter from 0 (4)

Foundation of Flutter layout
5 Write an open Source Chinese Client based on Flutter from 0 (5)

Set up the overall layout framework of the App
6 Write an open Source Chinese Client based on Flutter from 0 (6)

Implementation of various static pages
7 Write an open Source Chinese Client based on Flutter from 0 (7)

App network requests and data stores
👉 8 Write an open Source Chinese Client based on Flutter from 0 (8)

Use of plug-ins

Search for plug-in packages

To use a plugin, you need to know the name and version of the plugin. Flutter provides a plugin repository that you can search for. The repository address is: Pub.dartlang.org/, but this site may not be accessible in China. There is a website that Flutter provides for Chinese developers: pub.flutter-io.cn/. After the website is opened, you can directly search the keyword in the input box, as shown in the picture below:

For example, if we need to load a web page in an app using a WebView, we can search for ‘WebView’, or if we need to call the image library to select an image, we can search for ‘image picker’, there may be a lot of results, how do we choose the right plugin?

Since we are developing the Flutter application, we filter out the plugins for Flutter in the search results, as shown below:

Filtering is the first step. After filtering, you should also check the update date of the plugin package. The update date should not be long ago, because the plugin package released a long time ago may not be suitable for the current version of Flutter.

After filtering the above two steps, select the plugin you think is appropriate, click on it to see the details, the plugin description, sample usage, make sure you can do what you need, and you can happily add dependencies to your project.

Almost every plugin’s home page has instructions on how to add dependencies to the project. For example, in our Open Source Chinese client with Flutter, the plugin Flutter_webview_plugin has instructions on how to introduce dependencies:

Use the flutter_webview_plugin plug-in

In the Open source China Client project based on The Flutter, the user login page and information details page are loaded using the WebView. The plugin flutter_webview_plugin is used. The main function of the plug-in is to load a WebView on the Webpage of the Flutter and listen to various states of the WebView, such as loading, loading completed, etc., and can read cookies in the WebView or call JS methods in the WebView through dart code.

The oAuth-based certification process provided by Open Source China is generally as follows:

  1. Add the application in the background of open source China, improve the information of the application, the most important is the callback address, which will be used in the later;
  2. Use a browser or WebView to load the third-party authentication page, and enter the user name and password of Open Source China (the page for entering the password is provided by Open Source China, and the third party cannot obtain the password information).
  3. After entering the user name and password, click the login button on the page. If the login is successful, it will jump to the callback address we configured in the background in the first step and pass a code parameter to the page (the code parameter is directly splicing on the URL).
  4. In this page receive the code parameter and according to the open source China background providedclient_id client_secret(This step is just a GET request, but on my own server);
  5. After the above request is successful, open Source China’s OpenAPI will return information such as token and pass this information through one of JS’s in our callback pageget()Method is exposed for dart code to call.

For details of the oAuth certification process, see the open Source China documentation: Document address

Constructing the login page

Dart in lib/pages/ and use the WebviewScaffold component provided by the Flutter_webview_plugin plugin to render a WebView on the page for loading a URL. The code looks like this:

  @override
  Widget build(BuildContext context) {
    List<Widget> titleContent = [];
    titleContent.add(new Text(
      "Log on to Open Source China",
      style: new TextStyle(color: Colors.white),
    ));
    if(loading) {/ / if still in loading, are displayed on the title bar and a circular progress bar titleContent. Add (new CupertinoActivityIndicator ()); } titleContent. Add (new Container (width: 50.0)); // WebviewScaffold is a component provided by the plugin that displays a WebView on the page and loads the URLreturnNew WebviewScaffold(key: _scaffoldKey, URL: constants.login_URL, // appBar: new appBar (title: const) new Row( mainAxisAlignment: MainAxisAlignment.center, children: titleContent, ), iconTheme: new IconThemeData(color: Colors.white), ), withZoom:true, // allow web page scaling withLocalStorage:true, // allow LocalStorage withJavascript:true, // allow execution of JS code); }Copy the code

In the above code, we add a title to the AppBar component and a circular progress bar to indicate the loading status of the WebView. If the progress bar is loaded, it is displayed, otherwise it is hidden (so the LoginPage class should inherit the StatefulWidget).

Listen for WebView loading status and URL changes

The API provided by flutter_webview_plugin can listen for WebView loading status and URL changes. The main code is as follows:

// The login page, Class LoginPage extends StatefulWidget {@Override State<StatefulWidget> createState() => new LoginPageState(); } class LoginPageState extends State<LoginPage> {bool loading =true; Bool isLoadingCallbackPage = bool isLoadingCallbackPage =false; GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey(); StreamSubscription<String> _onUrlChanged; StreamSubscription<WebViewStateChanged> _onStateChanged; StreamSubscription<WebViewStateChanged> _onStateChanged; FlutterWebviewPlugin = new FlutterWebviewPlugin(); FlutterWebviewPlugin = New FlutterWebviewPlugin(); @override voidinitState() { super.initState(); // Listen for WebView load event, this listener is not active, No callback _onStateChanged = flutterWebViewPlugin. OnStateChanged. Listen ((WebViewStateChanged state) {/ / state. The type is an enumeration type, the values are: WebViewState.shouldStart, WebViewState.startLoad, WebViewState.finishLoad switch (state.type) {caseWebViewState. ShouldStart: / / ready to loadsetState(() {
            loading = true;
          });
          break;
        caseWebviewstate. startLoad: // Start loadingbreak;
        caseWebviewstate. finishLoad: // Loading is completesetState(() {
            loading = false;
          });
          if(isLoadingCallbackPage) {// This is the callback page, then call js method parseResult(); }break; }}); _onUrlChanged = flutterWebViewPlugin. OnUrlChanged. Listen ((url) {/ / login success will jump to the custom callback page, The page address to http://yubo725.top/osc/osc.php? Code = XXX // The page will receive the code, and then exchange the AccessToken according to the code, and return the obtained token and other information through the JS get() methodif(url ! = null && url.length > 0 && url.contains("osc/osc.php? code=")) {
        isLoadingCallbackPage = true; }}); }}Copy the code

The logic of the above code is:

  • Monitor the loading status of WebView and control the change of loading to change the progress bar on AppBar;
  • If the page URL contains osc/osc.php? Code = “, which means that the account password of open Source China is verified, and jump to our custom callback page. Here, set the value of isLoadingCallbackPage to true, which means that the callback page is currently loaded;
  • In the WebView’s WebViewState.finishLoad state, determine if the current page is a callback page, then passparseResult()Method calls js code to get the token information.

Dart calls the JS code to obtain the token information

The flutter_webview_plugin plugin provides an API for making dart js calls. Here is the parseResult() method:

// Parse the WebView data voidparseResult() {
    flutterWebViewPlugin.evalJavascript("get();").then((result) {// result JSON string containing token informationif(result ! = null && result.length > 0) {// what the fuck?? need twice decode?? var map = json.decode(result); // s is Stringif (map is String) {
            map = json.decode(map); // map is Map
          }
          if(map ! Datautils.savelogininfo (map) {// Login successfully, get token, close current page datautils.savelogininfo (map); Navigator.pop(context,"refresh");
          }
        } catch (e) {
          print("parse login result error: $e"); }}}); }Copy the code

The main method is flutterWebViewPlugin. EvalJavascript () of the incoming parameter is a string, said js code to be executed. The code above means to execute the get() method on the page, which returns information such as tokens, and then parse this information in the THEN and call datautils.savelogininfo (map); Save the login information, which brings me to the data saving I recorded in the last post. Navigator. Pop (context, “refresh”) is called after the data is saved; Method pushes the current page off the stack, and what does the “refresh” parameter do?

Notification of successful login to the previous page and refresh the previous page

The purpose of “refresh” is to make the previous page refresh (this is just a string argument; it’s up to you to define it). For those of you who have done Android development, you’ll be familiar with the idea that you’re passing data from the current page to the previous page, which typically starts the current page with the startActivityForResult method, and which receives parameters in the onActivityResult callback method. Flutter works a little bit like this, when opening the landing page in my page, use the following method:

_login() async {final result = await navigator.of (context).push(new MaterialPageRoute(Builder: (context) {returnnew LoginPage(); })); / / the result is"refresh"The login is successful.if(result ! = null && result =="refresh") {// refresh the user information getUserInfo(); Constants.eventbus.fire (new LoginEvent()); }}Copy the code

The push method of the Navigator returns a Future object, so we can process the information returned by the landing page in then. The ‘refresh’ string passed in when the landing page pops will be received here. Upon receipt, you can refresh the “me” page (refresh the user’s nickname and profile picture).

Use the event_bus plug-in

In the last _login() method above, we received the “refresh” parameter, fetched and refreshed the user information of the page, and then called a line of code to refresh the page:

Constants.eventBus.fire(new LoginEvent());
Copy the code

This line of code uses another framework: event_bus

If you have done Android development or front-end development, you should be familiar with this framework. EventBus is a publish/subscribe framework for subscribing to an event on a page and then firing the event somewhere else. The method of subscribing to the event is executed.

The main page of the framework in the pub warehouse is: pub.flutter-io.cn/packages/ev…

The use of this plug-in is simple, starting with importing packages:

import 'package:event_bus/event_bus.dart';
Copy the code

To subscribe to an event, use the following code:

New EventBus().on(MyEvent).listen((event) {// handle the event});Copy the code

MyEvent is a custom class that represents a single event. If you want to listen for all events, you can pass no arguments in the on method.

To send an event, use the following code:

new EventBus().fire(new MyEvent());
Copy the code

Use the fire method to send an event. The argument is the custom event object. You can add any argument you want to the object.

Based on the Flutter open source client project in China, can only to an EventBus object, no need at the time of each use all new EventBus (), so we in the lib/constants/the dart defines a static EventBus variables, This object can be shared globally:

static EventBus eventBus = new EventBus();
Copy the code

After a successful login, call the following code to notify the dynamic list to refresh:

Constants.eventBus.fire(new LoginEvent());
Copy the code

LoginEvent is an empty class that represents a successful LoginEvent.

On the flex list page, add a listener for successful logins:

Constants.eventBus.on(LoginEvent).listen((event) {
  setState(() {
    this.isUserLogin = true;
  });
});
Copy the code

The flexible list page loads different pages according to the isUserLogin variable above. If the variable is false, it indicates that no login is currently taking place, and the following interface is displayed:

If this variable is true, the open Source China API will be called to obtain the action information. The following interface is displayed:

The loading of the dynamic list will not be explained in detail here, but will be linked to the source code at the end of the article.

Use the image_Picker plug-in

On the sending page, there is the function of selecting pictures, as shown in the picture below:

The Flutter does not provide an API for us to manipulate the gallery of mobile devices, so we use the image_Picker plugin, which is located here: pub.flutter-io.cn/packages/im…

The code to import the plug-in is as follows:

import 'package:image_picker/image_picker.dart';
Copy the code

The plug-in is also easy to use, as follows:

// sourceImagefile = imagepicker.pickimage (imagefile = imagepicker.pickimage (imagefile = imagepicker.pickimage (ImageSource.source: source);
Copy the code

The pop-up menu at the bottom is displayed

The pop-up menu shown above has a built-in component in the Flutter that can be used directly. When we click âž• to select an image, we call the pickImage method with the following code:

PickImage (CTX) {num size = filelist.length;if (size >= 9) {
      Scaffold.of(ctx).showSnackBar(new SnackBar(
        content: new Text("You can only add up to 9 images!")));return; } // API provided by Flutter to display a popup Dialog showModalBottomSheet<void>(context: context, builder: _bottomSheetBuilder); } // Customize the bottom menu layout Widget _bottomSheetBuilder(BuildContext Context) {returnNew Container(height: 182.0, child: new Padding(Padding: const edgeinset.fromltrb (0.0, 30.0, 0.0, 30.0), child: new Column( children: <Widget>[ _renderBottomMenuItem("The camera takes pictures."Divider(height: 2.0,), _renderBottomMenuItem(Divider: 2.0,)"Gallery Selection Photos", ImageSource.gallery) ], ), ) ); } // Render the bottom menu for each item _renderBottomMenuItem(title, ImageSource)source) {var item = new Container(height: 60.0, child: new Center(child: new Text(title)),);returnNew InkWell(child: item, onTap: () {// Click on the item, close the bottom popover and call the camera or gallery navigater.of (context).pop();setState(() {
          _imageFile = ImagePicker.pickImage(source: source); }); }); }Copy the code

The _imageFile in the above code is a Future

object, because the operation of selecting the image is asynchronous, so where to receive the selected image? Whether it’s a photo or a library, finally call imagepicker.pickImage (source: In the example code on the Image_Picker home page, the component returns a FutureBuilder

object that receives the returned image File in its Builder method.

In the Open Source China client project based on Flutter, the image that receives the selection is placed in the build method. The build method code for the PublishTweetPage page is as follows:

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Release move", style: new TextStyle(color: Colors.white)),
        iconTheme: new IconThemeData(color: Colors.white),
        actions: <Widget>[
          new Builder(
            builder: (ctx) {
              returnNew IconButton(icon: new icon (icon.send), onPressed: () {// Send to datautils.islogin ().then((isLogin) {if (isLogin) {
                    return DataUtils.getAccessToken();
                  } else {
                    returnnull; } }).then((token) { sendTweet(ctx, token); }); }); },)],), // Receive the selected image here body: new FutureBuilder(Future: _imageFile, Builder: (BuildContext context, AsyncSnapshot<File> snapshot) {if(snapshot.connectionState == ConnectionState.done && snapshot.data ! = null && _imageFile ! = null) {// Select the image (photo or gallery) and add it to the List filelist.add (snapshot.data); _imageFile = null; } // The widget returnedreturngetBody(); },),); }Copy the code

A button has been added to the right side of the AppBar for sending the action message. The body section returns a FutureBuilder object that receives the selected image file in its Builder method, adds it to the list of images, and then calls the getBody() method to return the entire page. The reason for this is that each time an image is selected, the page needs to be refreshed. The fileList variable is used in the getBody() method. The getBody() method code looks like this:

  Widget getBody() {var textField = new textField (decoration: new InputDecoration(hintText:"Say something.", hintStyle: new TextStyle( color: const Color(0xFF808080) ), border: new OutlineInputBorder( borderRadius: Const Borderradius. all(const radius. circular(10.0)))), // Max. 6 lines can be typed) maxLines: 6, // Max. 150, // _controller.text is used to get the text input in the input box controller: _controller,); Var gridView = new Builder(Builder: (CTX) {returnNew gridView. count(// score 4 columns) crossAxisCount: 4, children: New list.generate (filelist.length + 1, (index) {// This method body is used to generate an item var content in the GridView;if(index == 0) {var addCell = new Center(child: new image.asset ('./images/ic_add_pics.png', width: 80.0, height: 80.0,)); Content = new GestureDetector(onTap: () {// Add image pickImage(CTX); }, child: addCell, ); }else{// select Image content = new Center(child: new image. file(fileList[index - 1], width: 80.0, height: 80.0, fit: BoxFit.cover,) ); }returnNew Container(margin: const EdgeInsets. All (2.0), width: 80.0, height: 80.0, color: const color (0xFFECECEC), child: content, ); })); }); var children = [ new Text("Tip: Due to OSC's OpenAPI limitations, only one image can be uploaded to the Posting API. This project can add up to nine images, but OSC will only accept the last image.", style: new TextStyle(fontSize: 12.0),), textField, new Container(margin: Const EdgeInsets. FromLTRB (0.0, 10.0, 0.0, 0.0), height: 200.0, child: gridView)];if(isLoading) {// Loading children. Add (new Container(margin: Const EdgeInsets. FromLTRB (0.0, 20.0, 0.0, 0.0), the child: new Center (child: new CircularProgressIndicator (),),)); }elseMSG children.add(new Container(margin: const EdgeInsets. FromLTRB (0.0, 20.0, 0.0, 0.0), child: new Center( child: new Text(msg), ) )); }returnNew Container(padding: const edgeinset.all (5.0), child: new Column(children: children,),); }Copy the code

After the selected images and the input content are obtained, the next step is to send the images, which calls the openapi of open source China. Here is the problem of using dart to upload the images.

SendTweet (CTX, token) async {// SnackBar prompt user when not logged in or not typedif (token == null) {
      Scaffold.of(ctx).showSnackBar(new SnackBar(
        content: new Text("Not logged in!")));return;
    }
    String content = _controller.text;
    if (content == null || content.length == 0 || content.trim().length == 0) {
      Scaffold.of(ctx).showSnackBar(new SnackBar(
        content: new Text("Please enter action content!"))); } try {Map<String, String> params = new Map(); params['msg'] = content;
      params['access_token'] = token; Var request = new MultipartRequest() var request = new MultipartRequest()'POST', Uri.parse(Api.PUB_TWEET));
      request.fields.addAll(params);
      if(fileList ! = null && filelist.length > 0) {// The interface provided by open source China only receives one imagefor (File f inVar stream = new http.byteStream (delegatingStream.typed (f.openread ())); var stream = new delegatingStream.typed (f.openread ()); Var length = await f.length(); Var filename = f.paath. Substring (f.paath. LastIndexOf (f.paath."/") + 1); Request.files.add (new http.multipartFile ())'img', stream, length, filename: filename)); }}setState(() {
        isLoading = true; }); Var response = await request.send(); / / parse the request returns the response data. The stream. The transform (utf8. Decoder). Listen ((value) {print(value);
        if(value ! = null) { var obj = json.decode(value); var error = obj['error'];
          setState(() {
            if(error ! = null && error =='200') {// SuccesssetState(() {
                isLoading = false;
                msg = "Successful release";
                fileList.clear();
              });
              _controller.clear();
            } else {
              setState(() {
                isLoading = false;
                msg = "Release failed:$error"; }); }}); }}); } catch (exception) {print(exception); }}Copy the code

The code for uploading images using Dart is completely different from a normal GET/POST Request. Uploading images requires constructing a Request object:

var request = new MultipartRequest('POST', Uri.parse(Api.PUB_TWEET));
Copy the code

To add a common argument, call request.field. AddAll:

request.fields.addAll(params); // Params is the parameter mapCopy the code

To add a file argument, call the request.files.add method:

request.files.add(new http.MultipartFile(
    'img', stream, length, filename: filename));
Copy the code

To parse the returned data, use the following code:

Var response = await request.send(); / / parse the request returns the response data. The stream. The transform (utf8. Decoder). Listen (value) ({})Copy the code

For the detailed code for sending a move, you can refer to the source code link at the end of the article, which will not be explained here.

The source code

All source code related to this article is available on GitHub for the Flutter – OSC project.

Afterword.

  • This article mainly documents the use of various plug-ins in the Open source Chinese client app based on Flutter.

  • The use of qr code scanning plug-ins is not recorded in this article, you can search the use of plug-ins on the pub.

  • This series of blogs does not document how all the features are implemented, but only selectively.

  • There are still many functions in this project that have not yet been implemented, such as big picture preview, personal information page display and so on. Most of the features are loaded in the form of WebView, so overall the implementation of the app is not complicated, and the amount of code is not much. It is open source, hoping to help those who study The Flutter. (If it helps, please support start at github 😂)

  • There are also some known and unknown bugs in this project. The known bug is that the tokens are not automatically refreshed after they expire (the tokens given by open source China have their expiry date, and refresh_token is required to refresh access_token after they expire). Some unknown bugs may cause ANR during app operation. Since we haven’t tested each model, we don’t know what causes ANR at the moment. However, during the development process, plug-ins will occasionally report errors. Thank you for your support!

My open source project

  1. Google Flutter based on the open source Chinese client, hope you give a Star support, source code:
  • GitHub
  • Yards cloud

  1. Tetris based on the Flutter small game, I hope you give a Star support, source:
  • GitHub
  • Yards cloud

In the previous
Write an open Source Chinese Client based on Flutter from 0 (7)

— App network requests and data stores