[Huawei Developers] Freemind R:
More articles like this, you can visit HUAWEI Developer Forum.​

In this article, we will build Android App Bundle (AAB).

In the previous article, we have learned about Huawei Dynamic Ability. Refer the URL:

https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201297090480680014&fid=0101187876626530001

If you have not read my previous article which is part 1 of this article. I would recommend you to kindly go through the link below:

https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201297090480680014&fid=0101187876626530001

1. Dynamic Ability:

Dynamic Ability is a service in which HUAWEI AppGallery implements dynamic loading based on the Android App Bundle technology.

Apps integrated with the Dynamic Ability SDK can dynamically download features or language packages from HUAWEI AppGallery as required, reducing the unnecessary consumption of network traffic and device storage space.

2. Android App Bundle:

It is a new upload format that includes all your apps compiled code and resources, but accepts APK generation and signing to AppGallery. Traditionally, Android apps are distributed using a special file called an Android Package (.apk).

Let’s start development:

We have created following project directory:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717062036.79586387038212296151678477744351:50510723012159:2800:19EBB7B5ADBF5006826180311A105EF4C0BEADCE0BF51BA04FB02ECC023FF885.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717062036.79586387038212296151678477744351:50510723012159:2800:19EBB7B5ADBF5006826180311A105EF4C0BEADCE0BF51BA04FB02ECC023FF885.png[/IMG]

Let’s see the gradle file:

apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'

android {
compileSdkVersion 28
buildToolsVersion "28.0.3"

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

dataBinding {
enabled = true
}

defaultConfig {
applicationId "com.hms.manoj.aab"
minSdkVersion 26
targetSdkVersion 28
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

dynamicFeatures = [":repository", ":retrofit", ":viewmodel"]

}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

// Android MVVM Components
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'

// Android Support Library
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha04'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

//Huawei Dynamic ability
api 'com.huawei.hms:dynamicability:1.0.11.302'

}

Let’s see the manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hms.manoj.aab">

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

<application
android:name="com.hms.manoj.aab.MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name="com.hms.manoj.aab.ui.ShowsActivity"
android:launchMode="singleTask"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustNothing">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name="com.hms.manoj.aab.ui.ShowDetailActivity"
android:screenOrientation="fullSensor"
android:label="@string/app_name" />
<activity
android:name="com.hms.manoj.aab.ui.CastActivity"
android:screenOrientation="fullSensor"
android:label="@string/app_name" />

</application>

</manifest>

Let’s see the implementation of feature installation inside the code:

package com.hms.manoj.aab.ui;

import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.GridLayoutManager;

import com.hms.manoj.aab.R;
import com.hms.manoj.aab.apiconnector.response.Show;
import com.hms.manoj.aab.databinding.ActivityShowsBinding;
import com.hms.manoj.aab.ui.adapter.ShowAdapter;
import com.hms.manoj.aab.viewmodel.ShowsViewModel;
import com.huawei.hms.feature.install.FeatureInstallManager;
import com.huawei.hms.feature.install.FeatureInstallManagerFactory;
import com.huawei.hms.feature.listener.InstallStateListener;
import com.huawei.hms.feature.model.FeatureInstallException;
import com.huawei.hms.feature.model.FeatureInstallRequest;
import com.huawei.hms.feature.model.FeatureInstallSessionStatus;
import com.huawei.hms.feature.model.InstallState;
import com.huawei.hms.feature.tasks.FeatureTask;
import com.huawei.hms.feature.tasks.listener.OnFeatureCompleteListener;
import com.huawei.hms.feature.tasks.listener.OnFeatureFailureListener;
import com.huawei.hms.feature.tasks.listener.OnFeatureSuccessListener;

import java.util.List;

import static com.hms.manoj.aab.utils.Utils.KEY_SHOW_ID;

public class ShowsActivity extends AppCompatActivity {

public static final String TAG=ShowsActivity.class.getName();

private FeatureInstallManager mFeatureInstallManager;

private int sessionId = 10086;

private ActivityShowsBinding mBinding;
private ShowsViewModel mShowsViewModel;

private InstallStateListener mStateUpdateListener = new InstallStateListener() {
@Override
public void onStateUpdate(InstallState state) {
Log.d(TAG, "install session state " + state);
if (state.status() == FeatureInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
try {
mFeatureInstallManager.triggerUserConfirm(state, ShowsActivity.this, 1);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return;
}

if (state.status() == FeatureInstallSessionStatus.REQUIRES_PERSON_AGREEMENT) {
try {
mFeatureInstallManager.triggerUserConfirm(state, ShowsActivity.this, 1);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return;
}

if (state.status() == FeatureInstallSessionStatus.INSTALLED) {
Log.i(TAG, "installed success ,can use new feature");
return;
}

if (state.status() == FeatureInstallSessionStatus.UNKNOWN) {
Log.e(TAG, "installed in unknown status");
return;
}

if (state.status() == FeatureInstallSessionStatus.DOWNLOADING) {
long process = state.bytesDownloaded() * 100 / state.totalBytesToDownload();
Log.d(TAG, "downloading percentage: " + process);
return;
}

if (state.status() == FeatureInstallSessionStatus.FAILED) {
Log.e(TAG, "installed failed, errorcode : " + state.errorCode());
return;
}

}
};

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setTitle("All Shows");
mFeatureInstallManager = FeatureInstallManagerFactory.create(this);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_shows);
mShowsViewModel = ViewModelProviders.of(this).get(ShowsViewModel.class);

wait(true);
getShowList();
}

public void installFeature(View view) {
if (mFeatureInstallManager == null) {
return;
}
FeatureInstallRequest request = FeatureInstallRequest.newBuilder()
.addModule("repository")
.addModule("retrofit")
.addModule("viewmodel")
.build();
final FeatureTask<Integer> task = mFeatureInstallManager.installFeature(request);
task.addOnListener(new OnFeatureSuccessListener<Integer>() {
@Override
public void onSuccess(Integer integer) {
Log.d(TAG, "load feature onSuccess.session id:" + integer);
}
});
task.addOnListener(new OnFeatureFailureListener<Integer>() {
@Override
public void onFailure(Exception exception) {
if (exception instanceof FeatureInstallException) {
int errorCode = ((FeatureInstallException) exception).getErrorCode();
Log.d(TAG, "load feature onFailure.errorCode:" + errorCode);
} else {
exception.printStackTrace();
}
}
});
task.addOnListener(new OnFeatureCompleteListener<Integer>() {
@Override
public void onComplete(FeatureTask<Integer> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to start install.");
if (featureTask.isSuccessful()) {
Integer result = featureTask.getResult();
sessionId = result;
Log.d(TAG, "succeed to start install. session id :" + result);
} else {
Log.d(TAG, "fail to start install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
Log.d(TAG, "start install func end");
}

private void wait(boolean isLoading) {
if (isLoading) {
mBinding.loaderLayout.rootLoader.setVisibility(View.VISIBLE);
mBinding.layout.shows.setVisibility(View.GONE);
} else {
mBinding.loaderLayout.rootLoader.setVisibility(View.GONE);
mBinding.layout.shows.setVisibility(View.VISIBLE);
}
}

private void getShowList() {
mShowsViewModel.getShowsLiveData().observeForever(showList -> {
if (showList != null) {
wait(false);
String eventName = "Show Success";
Bundle bundle = new Bundle();
bundle.putInt("Show Size", showList.size());
setDataIntoAdapter(showList);
} else {
wait(false);
}
});
}

private void setDataIntoAdapter(List<Show> list) {
mBinding.layout.shows.setLayoutManager(new GridLayoutManager(this, 2));
mBinding.layout.shows.setAdapter(new ShowAdapter(list, (item) -> {
Intent intent = new Intent(getBaseContext(), ShowDetailActivity.class);
intent.putExtra(KEY_SHOW_ID, String.valueOf(item.getId()));
String eventName = "Choosed Show";
Bundle bundle = new Bundle();
bundle.putInt("Show ID", item.getId());
startActivity(intent);
}));
}

@Override
protected void onResume() {
super.onResume();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.registerInstallListener(mStateUpdateListener);
}
}

@Override
protected void onPause() {
super.onPause();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.unregisterInstallListener(mStateUpdateListener);
}
}
}

We will create 3 Dynamic Feature module under android project.

1. Repository Module

2. Network Module

3. ViewModel Module

Let’s create Repository Module:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061825.64803306115519051009279032483273:50510723012159:2800:05C7F44D234F01BF636C0697491C3A0611973647EAE351B18050B304F02D3A59.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061825.64803306115519051009279032483273:50510723012159:2800:05C7F44D234F01BF636C0697491C3A0611973647EAE351B18050B304F02D3A59.png[/IMG]

In this module, we have created ShowRepository class which communicate between network and UI components.

Let’s see gradle file:

apply plugin: 'com.android.dynamic-feature'
android {
compileSdkVersion 29

defaultConfig {
minSdkVersion 27
targetSdkVersion 29
versionCode 1
versionName "1.0"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':app')
// RxAndroid
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
}

Note: Module should have this in the first line, as it is a dynamic feature:

apply plugin: 'com.android.dynamic-feature'

Let’s see manifest file of this module:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.hms.android.repository">

<dist:module
dist:instant="false"
dist:title="@string/title_repository">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
</manifest>

Let’s create Network Module:

In this module, we have implemented all business model.

Let’s see gradle file:

apply plugin: 'com.android.dynamic-feature'

android {
compileSdkVersion 29
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
defaultConfig {
minSdkVersion 27
targetSdkVersion 29
versionCode 1
versionName "1.0"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':app')
// RxAndroid
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

// Android MVVM Components
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
}

Let’s see manifest file of this module:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.hms.android.viewmodel">
<dist:module
dist:instant="false"
dist:title="@string/title_viewmodel">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
</manifest>

Let’s build Android App Bundle:

We need to follow the following steps to generate .aab file.

Follow the steps to generate aab file.

1. Step: Goto->Build->Build Bundle(s)/APK(s)->Build Bundle(s). Choose Build > Build Bundle(s)/APK(s) > Build Bundle(s)

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061212.02353875436814730946408894898756:50510723012159:2800:35D19664B3A48E4F602946F0129B93BB7E27C6D9A8922F128E11791097694482.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061212.02353875436814730946408894898756:50510723012159:2800:35D19664B3A48E4F602946F0129B93BB7E27C6D9A8922F128E11791097694482.png[/IMG]

2. After build success, following dialog-box displayed.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061103.69516401705377212749807742801016:50510723012159:2800:46EC9C4C9331C0DE7964DD07214DAFD943821EB7163837147A2747550DA57E77.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061103.69516401705377212749807742801016:50510723012159:2800:46EC9C4C9331C0DE7964DD07214DAFD943821EB7163837147A2747550DA57E77.png[/IMG]

3. Click locate text and the output folder will be displayed.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061033.97460180556483975976271857467827:50510723012159:2800:A0CF7F91791144A42389BB681E5C84283EFC2446B8430D773A49BA2FD44B8B4A.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717061033.97460180556483975976271857467827:50510723012159:2800:A0CF7F91791144A42389BB681E5C84283EFC2446B8430D773A49BA2FD44B8B4A.png[/IMG]

Note: Singing the aab file carefully and upload on AppGallery portal.

4. Provide your project/app information in AppGallery portal:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717064527.61372330538620103409253432388455:50510723012159:2800:6DF3471E32A31099B3A4B7C4A5BE7F2A5A9074BCC65162D5C8B5561F2AA13B57.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717064527.61372330538620103409253432388455:50510723012159:2800:6DF3471E32A31099B3A4B7C4A5BE7F2A5A9074BCC65162D5C8B5561F2AA13B57.png[/IMG]

5. Upload your aab/apk file in draft section.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717064657.76395215077475042405190653616721:50510723012159:2800:16F44DC70EDA11BADC5E99BBDECB76628506D6B7CA8B8283BE8E2175819AC987.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200717064657.76395215077475042405190653616721:50510723012159:2800:16F44DC70EDA11BADC5E99BBDECB76628506D6B7CA8B8283BE8E2175819AC987.png[/IMG]

Now We can download the application from app gallery(After approval).

Time being, we can run that apk in our mobile device.

Let's see the result.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200420063305.05802645821261768793499117935556:50510723012159:2800:84C62AD91A513665709F9EAC340564BDA634BC7CC671A0782174D8ADF4649A01.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200420063305.05802645821261768793499117935556:50510723012159:2800:84C62AD91A513665709F9EAC340564BDA634BC7CC671A0782174D8ADF4649A01.png[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200420063120.60957687438592529799291403389314:50510723012159:2800:57A190F83B20934CA53407DF7D47898A8168B12E2801511E12F6C8D52D82ADF6.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200420063120.60957687438592529799291403389314:50510723012159:2800:57A190F83B20934CA53407DF7D47898A8168B12E2801511E12F6C8D52D82ADF6.png[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200420063441.20051105960504327925140039522527:50510723012159:2800:23B3243D28EA6FC40B32832E8940572A75EF1FC6238A0E90EB0EC8C91ED1F322.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200420063441.20051105960504327925140039522527:50510723012159:2800:23B3243D28EA6FC40B32832E8940572A75EF1FC6238A0E90EB0EC8C91ED1F322.png[/IMG]

If you have any doubts or queries. Leave your valuable comment below.