Flutter Programming with Android AAR File

Flutter, a framework developed by Google, aims to help developers build iOS and Android apps from a single codebase in Dart programming language. It is still an early-stage open-source project. The development environment only works on macOS and Linux (64-bit). I was curious about how Flutter performs comparing to Xamarin and Cordova, and thus, I got started with Flutter a week ago. When developing an Android app, you may need to use some third-party SDKs, which wrapped as *.jar, *.so or *.aar files. In this post, I will share how to link an Android aar file in Flutter project.

How to Add AAR File to Flutter Project

Download Dynamsoft Barcode Reader for Android to get DynamsoftBarcodeReader.aar file.

Download Flutter source code.

Automatically configure aar file with Android Studio for Flutter project

Import flutter/examples/hello_services/android/ to Android Studio.

Click File > New > New Module and choose Import .JAR/.AAR Package to import DynamsoftBarcodeReader.aar to the Android project.

Press F4 to open Project Structure, and then add the dependent module.

Manually configure aar file for Flutter project

After setting aar file with Android Studio, open the project with IntelliJ IDEA to see what happened. After that, we can manually configure an aar file in Flutter project as follows:

  1. Create a new folder android/DynamsoftBarcodeReader.
  2. Create android/DynamsoftBarcodeReader/build.gradle:

     configurations.maybeCreate("default")
     artifacts.add("default", file('DynamsoftBarcodeReader.aar'))
    

    Copy DynamsoftBarcodeReader.aar to this folder:

    flutter android aar

  3. Edit android/settings.gradle:

     include ':app', ':DynamsoftBarcodeReader'
    
     Add the dependency to android\app\build.gradle:
        
     dependencies {
        
         androidTestCompile 'com.android.support:support-annotations:22.0.0'
        
         androidTestCompile 'com.android.support.test:runner:0.5'
        
         androidTestCompile 'com.android.support.test:rules:0.5'
        
         compile project(':DynamsoftBarcodeReader')
        
     }
    

Building Android Barcode Reader with Flutter

Open AndroidManifest.xml  to add permissions:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Write Java code to invoke Dynamsoft Barcode APIs and send result message to the Flutter UI:

private String onGetBarcode(String json) {
        String filename;
        try {
            JSONObject message = new JSONObject(json);
            filename = message.getString("filename");
        } catch (JSONException e) {
            Log.e(TAG, "JSON exception", e);
            return null;
        }

        String locationProvider;
        String barcodeResult = "No barcode detected";
        File file = new File(filename);
        if (!file.exists()) {
            barcodeResult = "No file exists: " + file.toString();
            Toast.makeText(BarcodeReaderActivity.this, barcodeResult, Toast.LENGTH_LONG).show();

            return null;
        }
        else {
            Bitmap bitmap = BitmapFactory.decodeFile(file.toString());
            BarcodeReader reader = new BarcodeReader("license");
            ReadResult result = reader.readSingle(bitmap, Barcode.QR_CODE);
            Barcode[] all = result.barcodes;
            if (all != null && all.length == 1) {
                barcodeResult = all[0].displayValue;
            }
            else {
                barcodeResult = "no barcode found: " + file.toString();
            }

            bitmap.recycle();

        }

        JSONObject reply = new JSONObject();
        try {
            if (barcodeResult != null) {
              reply.put("result", barcodeResult);
            } else {
              reply.put("result", "No barcode detected");
            }
        } catch (JSONException e) {
            Log.e(TAG, "JSON exception", e);
            return null;
        }

        return reply.toString();
    }

Create Flutter UI with Input, Button, and Text widgets:

@override
  Widget build(BuildContext context) {
    if (_isExisted) {
      return new Material(
          child: new Center(
              child: new Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    new Text('Barcode Reader'),
                    new Input(
                      labelText: 'Please input the image path',
                      value: new InputValue(text: _filename),
                      onChanged: onTextChanged,
                      autofocus: true,
                    ),
                    new Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: <Widget>[
                          new RaisedButton(
                              child: new Text('Read'),
                              onPressed: _getBarcode
                          ),
                          new RaisedButton(
                              child: new Text('Reset'),
                              onPressed: _resetResult
                          ),
                        ]
                    ),
                    new Image.file(new File(_filename)),
                    new Text('$_result'),
                  ]
              )
          )
      );
    }
    else {
      return new Material(
          child: new Center(
              child: new Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    new Text('Barcode Reader'),
                    new Input(
                      labelText: 'Please input the image path',
                      onChanged: onTextChanged,
                      autofocus: true,
                    ),
                    new Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: <Widget>[
                          new RaisedButton(
                              child: new Text('Read'),
                              onPressed: _getBarcode
                          ),
                          new RaisedButton(
                              child: new Text('Reset'),
                              onPressed: _resetResult
                          ),
                        ]
                    ),
                    new Text('$_result'),
                  ]
              )
          )
      );
    }
  }

  Future<Null> _readBarcode() async {
    final Map<String, String> message = <String, String>{'filename':_filename};
    final Map<String, dynamic> reply = await HostMessages.sendJSON('getBarcode', message);
    // If the widget was removed from the tree while the message was in flight,
    // we want to discard the reply rather than calling setState to update our
    // non-existent appearance.
    if (!mounted)
    return;
    setState(() {
    _result = reply['result'].toString();
    });
  }

Run the Flutter project:

flutter barcode reader

References

APK Building Issues

The Android APK file generated by Flutter project contains a native library libsky_shell.so compiled for armeabi-v7a. DynamsoftBarcodeReader.aar built for armeabi-v7a and arm64-v8a. If you unzip the APK file, you will see there is no libsky_shell.so file in arm64-v8a folder. It will cause a crash when running the App on Android devices with a 64-bit processor, like Qualcomm Snapdragon 820, because by default the operating system will load shared library from arm64-v8a folder.

flutter armeabi-v7a

flutter arm64-v8a

How to solve this issue? Remove native libraries other than armeabit-v7a and rebuild the aar file.

Source Code

https://github.com/yushulx/flutter-android-aar