Flutter engineering construction (Android end)

Posted by DustParticle on Mon, 03 Jan 2022 17:25:40 +0100

In order to actively embrace new technologies and optimize the performance of RN, it is decided to introduce the fluent technology stack into new business requirements

The development of Flutter hybrid stack can be roughly divided into the following two modes

native projects rely directly on development

The specific access method is as follows: first, in setting Add the following code to gradle:

setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir,
        '../../Flutter Module Project root directory/.android/include_flutter.groovy'
))

Secondly, in App build Add the following code to gradle:

 implementation project(':flutter')

Finally, in the main project build Add the following code to gradle:

repositories {
buildscript {
        maven {
            url 'http://download.flutter.io'
        }
    }
}

allprojects {
    repositories {
        maven {
            url 'http://download.flutter.io'
        }
    }
}

native project access aar

New fluent module project

flutter create -t module xx_module

The directory structure is as follows

xx_modlue 
          - .android // Android test project
          - .ios  // iOS Test Engineering
          - lib  // Flutter main works
                - main.dart // Fluent entry file
          - pubspec.yaml  // Flutter 3rd party package profile

The command to package module s into AARS is provided in fluent, and the generated aar file path is xx_modlue/build/host/outputs/repo

flutter build aar

The reference of aar can be completed by introducing the generated aar file into the Android development project

So far, the introduction of the whole aar can be developed normally, but there is a problem, that is, in each development, the generated aar package needs to be manually copied to the main project for dependency, which is not only troublesome but also prone to errors. Therefore, it is the best practice to turn the fluent packaging and introduction process into a common mode for daily development

Analysis of the process of flitter packaging and uploading:

In order to comply with the daily development process, the aar file printed by Flutter needs to be uploaded to maven, so the first task is to solve the problem of uploading aar to Maven

Looking at the pom file under the generated aar directory, you will find that the third-party aar package that the main project depends on will also be downloaded to XX_ Under the path of modlue / build / host / outputs / repo, the pom file is as follows:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.xxx.flutter</groupId>
  <artifactId>xxx</artifactId> 
  <version>release-0.0.7</version>
  <packaging>aar</packaging>
  <dependencies>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>flutter_embedding_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>armeabi_v7a_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>arm64_v8a_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>x86_64_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

By analyzing the pom file, we can see that when uploading the aar generated by the main project, we also need to upload the downloaded third-party aar to the maven library. Therefore, we know that the specific engineering script process is as follows:

1. Get the generated aar path
2. Upload the aar file that the third party depends on
3. Update artifactId of main project aar
4. Upload main project aar file

The specific script is as follows:

deploy_aar(){
    mvn deploy:deploy-file \
    -DpomFile="$FILE_PATH/$NAME.pom" \
    -DgeneratePom=false \
    -Dfile="$FILE_PATH/$NAME.aar" \
    -Durl="http://xxx.xxx.xxx:xxx/repository/public/" \
    -DrepositoryId="nexus" \
    -Dpackaging=aar \
    -s="mvn-settings.xml" \
    -Dversion="$VERSION"
}

projectDir=`pwd`

# Clear fluent generation file
flutter clean

# Get pub package
flutter pub get

# remove folders
rm -rf `pwd`/build/host/outputs/repo/


# Modified version number
group="com.xxx.flutter"
type="release"
#type="debug"
#type="profile"
version="${type}-0.0.7"
artifactId="xxx"


echo "replace Flutter/build.gradle Medium group by ${group}"
path=`pwd`/.android/Flutter/build.gradle
sed -i '' '29s/^.*$/group "'${group}'"/'  ${path}
echo "replace Flutter/build.gradle Medium version by ${version}"
path=`pwd`/.android/Flutter/build.gradle
sed -i '' '30s/^.*$/version "'${version}'"/'  ${path}


# Pack AAR
flutter build aar --no-debug --no-profile

# Find AAR and upload
path=`pwd`
# shellcheck disable=SC2006
p=`find ${path}/build/host/outputs/repo -type f  -name "*${type}*.aar"`
echo "${p}"


array=(${p//'\n'/})
currentName=""
currentPath=""
currentPom=""
currentDir=""

# shellcheck disable=SC2068
for item in ${array[@]}
do
    resFile=`basename ${item}`
    echo "${item}"
    result=$(echo ${resFile} | grep "flutter_release")
    if [[ "$result" == "" ]]
    then
      lenght=${#item}
      sub=${item:0:${lenght}-3}
      pom="${sub}pom"
      resFileLenght=${#resFile}
      subDir=${item:0:${lenght}-${resFileLenght}}
      curName=`echo ${resFile} | cut -d "-" -f 2`
      curNameLenght=${#curName}
      subVersion=${curName:0:${curNameLenght}-4}
      nameLenght="${#resFile}"
      subName=${resFile:0:${nameLenght}-4}
      export FILE_PATH="${subDir}"
      export NAME="${subName}"
      export VERSION=${subVersion}
      deploy_aar
    else
      nameLenght="${#resFile}"
      subName=${resFile:0:${nameLenght}-4}
      currentName="${subName}"
      currentPath=${item}
      currentPath=${item}
      lenght=${#item}
      sub=${item:0:${lenght}-3}
      currentPom="${sub}pom"
      resFileLenght=${#resFile}
      subDir=${item:0:${lenght}-${resFileLenght}}
      currentDir=${subDir}
    fi
done

cd "${currentDir}"
echo `pwd`
mv "${currentName}.aar" "${artifactId}-${version}.aar"
mv "${currentName}.pom" "${artifactId}-${version}.pom"
cd ${projectDir}
echo `pwd`

currentName="${artifactId}-${version}"
currentPath="${currentDir}${currentName}.aar"
currentPom="${currentDir}${currentName}.pom"
echo "current name is ${currentName}"
echo "current path is ${currentPath}"
echo "currentPom is ${currentPom}"
echo "replace pom artifactId by ${artifactId}"
sed -i '' '6s/^.*$/  <artifactId>'${artifactId}'<\/artifactId> /'  ${currentPom}
echo "currentDir is ${currentDir}"
echo "currentVersion is ${version}"


export FILE_PATH="${currentDir}"
export NAME="${currentName}"
export VERSION=${version}
deploy_aar

After uploading maven successfully, the main project relies on the fluent code, which is consistent with the process of adding a third-party SDK.

Selection comparison

name advantage shortcoming
native projects rely directly on development Fast access The engineering structure is complex, so it is impossible to separate the fluent development from the native development process
native project access aar Decoupling between fluent development and native development process The initial access process is complex

The final choice is to access aar through maven to facilitate subsequent expansion

Selection of Flutter hybrid stack

After completing the Flutter hybrid development access process, there will be a hybrid stack management problem. The main problem to be solved in the hybrid scheme is how to deal with the alternating Flutter and Native pages. Based on the current open source framework, the model is fluterboost

flutterBoost Flutter end access:

FlutterBoost.singleton.registerPageBuilders(<String, PageBuilder>{
      testhome: (String pageName, Map<dynamic, dynamic> params, String _) =>
          MyHomePage(title: ''),
      shoppingcar: (String pageName, Map<dynamic, dynamic> params, String _) {
        String platformItemNo = '';
        if (params.containsKey("platformItemNo")) {
          platformItemNo = params['platformItemNo'];
          NativeChat.print(platformItemNo);
        }
        return ShoppingCar(platformItemNo: platformItemNo);
      },
      login: (String pageName, Map<dynamic, dynamic> params, String _) =>
          LoginPage(),
      overlay: (String pageName, Map<dynamic, dynamic> params, String _) =>
          OverlayPage(),
    });

android access:

application initialization code:

val router =
            INativeRouter { context, url, urlParams, requestCode, exts ->
                PageRouter.openPageByUrl(context, url, urlParams)
            }

        val boostLifecycleListener = object : FlutterBoost.BoostLifecycleListener {
            override fun onEngineCreated() {
            }

            override fun onPluginsRegistered() {
            }

            override fun beforeCreateEngine() {
            }

            override fun onEngineDestroy() {
            }

        }

        val platform = FlutterBoost.ConfigBuilder(application, router)
            .isDebug(BuildConfig.DEBUG)
            .whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
            .renderMode(FlutterView.RenderMode.texture)
            .lifecycleListener(boostLifecycleListener)
            .build()
        FlutterBoost.instance().init(platform)

Routing configuration code

// PageRouter route jump and configuration page
object PageRouter {
    /**
     * Routing mapping
     */
    val pageName: HashMap<String?, String?> =
        object : HashMap<String?, String?>() {
            init {
                put("xxxx://shoppingCar", "shoppingCar")
                put("xxxx://login", "login")
                put("xxxx://home", "home")
                put("xxxx://overlay", "overlay")
            }
        }
    const val SHOPPING_CAR = "xxxx://shoppingCar"
    const val LOGIN_PAGE = "xxxx://login"
    const val OVERLAY = "xxxx://overlay"
    const val BUYER_PRODUCT_DETAIL = "xxxx://buyer/productdetail"
    const val TEST_SECOND = "xxxx://testSecond"

    @JvmOverloads
    fun openPageByUrl(
        context: Context,
        url: String,
        params: Map<*, *>?,
        requestCode: Int = 0
    ): Boolean {
        val path = url.split("\\?").toTypedArray()[0]
        Log.i("openPageByUrl", path)
        return try {
            when {
                pageName.containsKey(path) -> {
                    val intent =
                        BoostFlutterActivity.withNewEngine().url(pageName[path]!!)
                            .params(params!!)
                            .backgroundMode(BoostFlutterActivity.BackgroundMode.opaque)
                            .build(context)
                    if (context is Activity) {
                        context.startActivityForResult(intent, requestCode)
                    } else {
                        context.startActivity(intent)
                    }

                    return true
                }
                url.startsWith(TEST_SECOND) -> {
                    context.startActivity(
                        Intent(
                            context,
                            SecondActivity::class.java
                        )
                    )
                    return true
                }
                else -> false
            }
        } catch (t: Throwable) {
            false
        }
    }
}

native jump logic

// Initialize channel notification

 FlutterBoost.instance().channel().addMethodCallHandler { call, result ->
            when (call.method) {
                "baseUrl" -> {
                    result.success(ApiConstant.getApiUrl())
                }
            }
        }

// Jump code

 val params = hashMapOf<String, String>()
            params["param"] = param
            PageRouter.openPageByUrl(this, PageRouter.SHOPPING_CAR, params)

Setup of fluent test environment

The most likely way to make complaints about mixed development is testing. It is time-consuming and difficult to debug with native. It requires high level of front-end development and needs to understand the basic process of native.

Topics: Flutter