preface

This feature has been added to Bedrock's rapid development framework.Copy the code

Github.com/bladeofgod/…

Framework introduction:Copy the code

Bedrock — A quick development framework for Flutter based on MVVM+Provider

After the language

Update an app in the following ways: On the IOS terminal, switch to the App Store on the Android terminal, or download the app from the server and install it yourself. Let's implement the Android download and install function (actually I don't know IOS).Copy the code

Introduction to the

Flutter now also has several plug-ins to download and install:

flutter_downloader

install_plugin
Copy the code

However, I have learned that it is not a conflict with third-party plug-ins, or it is not updated for a long time, and then adapt to fix it is better to write one, but also more flexible.

The Flutter end

The plug-ins we need are:

Path_provider is used to get the storage path dio is used to download your installation package. Permission_handler is used to request permissionsCopy the code

permissions

First, we need to apply for permissions (Android needs to declare, this is said on Android) :

  checkPermission()async{
    Map<Permission,PermissionStatus> permissions =
    await [
      Permission.storage,
    ].request();
    if(permissions.values.first.isGranted){
      downloadAPK();

    }else{
      showToast('permissions denied'); }}Copy the code

download

The Download function of Dio is very easy to use. The specific methods are as follows:

  downloadAPK()async{
    try{
      await dio.download(url, getSavePath(),
          cancelToken:cancelToken,
          onReceiveProgress: (receive,total){
            //debugPrint('apk info $receive $total');
            setProgress((receive/total *100).toStringAsFixed(1));
          } ).then((response){
            if(response.statusCode == 200){ installAPK(); }}); } Catch (e,s){/// If cancelToken cancels the task, a dio exception will be thrown. Cancel // you can either catch and operate on it, or do nothing at all}}Copy the code

Brief Introduction to parameters:

The URL is the download path

CancelToken This parameter is optional. It can be used to cancel the download task

OnReceiveProgress is a callback, Receive is the size of the current download, and total is the size of the entire resource

GetSavePath () provides a save path as follows:

  String getSavePath(){
    String path = StorageManager.externalDirectory.path + '/test/bedrock.apk';
    return path;
  }
Copy the code

When we complete the download, we can proceed to the installation step, this part needs the cooperation of the native end to achieve, we first pull the Channel implementation of the native end.

BasicMessageChannel: used to pass strings and semi-structured information. This method is used less often: The method Invocation is usually used to invoke a native method EventChannel: Event Streams communication. There are monitoring functions, such as power changes that push data directly to the flutter terminal.Copy the code

Here we use method_channel to get through to the native end:

Create a channel:

static const MethodChannel _channel = const MethodChannel('com.lijiaqi.bedrock'); The constructor passes in a name that is arbitrary but consistent with the original.Copy the code

Define a method name:

static final String methodInstall = 'install_apk'; // This should also be consistent with the nativeCopy the code

We can then pull up the corresponding method on the native side:

// Path is the installation path of APK void installApk(String Path) Async {///ios You are advised to use the application market directlyif(Platform.isAndroid){
      await _channel.invokeMethod(methodInstall,
          {"path":path}); }}Copy the code

Let’s implement android

The Android end

Use Android Studio to open Android under the project and open it in sequence: app-src-main-java/kotlinCopy the code

I didn't learn Kotlin, so I wrote it in Java, so it's almost the same.Copy the code

Create a new package in the Java folder, name it whatever you want, I’ll call it com.lijiaqi

Then create a new one:

Mainactivity since the project defaults to Kotlin, I had to create a new plugin/BedrockPlugin for handling flutter requestsCopy the code

MainActivity

import com.lijiaqi.bedrock.plugin.BedrockPlugin; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); flutterEngine.getPlugins().add(new BedrockPlugin(this)); }}Copy the code

By:

flutterEngine.getPlugins().add(new BedrockPlugin(this));
Copy the code

We can register our BedrockPlugin with the following code

BedrockPlugin, there is quite a lot of code, so I have put annotations in the code for easy matching

public class BedrockPlugin implements FlutterPlugin, ActivityAware, MethodChannel. MethodCallHandler {/ / / here are consistent with the flutter end private static final String PLUGIN_NAME ="com.lijiaqi.bedrock"; ///channel private MethodChannel mMethodChannel; private Application mApplication; private WeakReference<Activity> mActivity; public BedrockPlugin(Activity mActivity) { this.mActivity = new WeakReference<Activity>(mActivity); } @override public void onAttachedToEngine(@nonnull FlutterPluginBinding binding) = new MethodChannel(binding.getBinaryMessenger(),PLUGIN_NAME); mApplication = (Application) binding.getApplicationContext(); mMethodChannel.setMethodCallHandler(this); } @override public void onDetachedFromEngine(@nonnull FlutterPluginBinding binding) mMethodChannel.setMethodCallHandler(null); mMethodChannel = null; @override public void onMethodCall(@nonnull MethodCall); @NonNull MethodChannel.Result result) {log("call"); Switch (call.method){/// Use call.method to get the name of the method that is invoked on the flutter side, be sure both ends are the samecase "install_apk"Call. Argument () invokeInstall(call. Argument ()"path"));
                break;
            default:
                break; Private static final String provider = private static final String provider ="com.lijiaqi.flutter_bedrock.fileProvider"; Before Android 7.0, you could create a File directly from the path to install it, but after Android 7.0, you need a fileProvider to help install it. Private void invokeInstall(String apkPath){if(apkPath ! = null && ! apkPath.isEmpty()){log(apkPath); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ///< check whether it is AndroidN or higherif(build.version.sdk_int >= build.version_codes.n) {// Can no longer be usedsetFlags, setflags will reset before setting, or multiple | setflags splicing, either addflag intent. AddFlags (intent. FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(mActivity.get(), provider, new File(apkPath)); intent.setDataAndType(contentUri,"application/vnd.android.package-archive");
            } else {
                intent.setDataAndType(Uri.fromFile(new File(apkPath)), "application/vnd.android.package-archive");
            }
            mActivity.get().startActivity(intent);

        }
    }

    private void log(String msg){
        Log.i("native method",msg); } / / / the @ Override the temporarily didn't have to consider the public void onAttachedToActivity (@ NonNull ActivityPluginBinding binding) {} @ Override public voidonDetachedFromActivityForConfigChanges() {

    }

    @Override
    public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {

    }

    @Override
    public void onDetachedFromActivity() {}}Copy the code

Androidmanifest.xml: androidmanifest.xml: androidmanifest.xml: androidManifest.xml: androidManifest.xml: androidManifest.xml: androidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
Copy the code

If you use Java like me and your project defaults to Kotlin, you’ll also need to change the name property in the <activity tag to the one we created ourselves.

After that, we add provider at the bottom of the < Application tag as follows:

Authorities, you can fill in the package name + any character, make sure it is the same as provider in the previous code.

Then we will create a new XML folder under the RES folder and create another XML file with whatever name we like. I will call it “Filepaths” and the contents are as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <resources> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="my_images" path="images"/> <external-path name="files" path="."/> <cache-path name="cache_place" path="."/> </paths>Copy the code

The whole function is now complete.

DEMO

The DEMO address

It can be run directly. In the “Comprehensive Demo demo”, there is an “update” button that you can click to get there.