Skip to content
This repository was archived by the owner on Feb 20, 2024. It is now read-only.

Commit a214ad7

Browse files
wojtek-kalicinskiWojtek Kaliciński
authored andcommitted
Dynamic code loading sample
0 parents  commit a214ad7

File tree

53 files changed

+2412
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2412
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
.DS_Store
5+
build/
6+
/captures
7+
.externalNativeBuild
8+
/.idea
9+
/keystore.properties

.google/packaging.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# GOOGLE SAMPLE PACKAGING DATA
16+
#
17+
# This file is used by Google as part of our samples packaging process.
18+
# End users may safely ignore this file. It has no relevance to other systems.
19+
---
20+
# Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED}
21+
status: PUBLISHED
22+
23+
# Optional, put additional explanation here for DEPRECATED or SUPERCEDED.
24+
# statusNote:
25+
26+
technologies: [Android]
27+
categories: [Dynamic Features]
28+
languages: [Kotlin]
29+
solutions: [Mobile]
30+
31+
github: googlesamples/android-dynamic-code-loading
32+
33+
# Values: BEGINNER | INTERMEDIATE | ADVANCED | EXPERT
34+
level: ADVANCED
35+
36+
license: apache2

CONTRIBUTING.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# How to become a contributor and submit your own code
2+
3+
## Contributor License Agreements
4+
5+
We'd love to accept your sample apps and patches! Before we can take them, we
6+
have to jump a couple of legal hurdles.
7+
8+
Please fill out either the individual or corporate Contributor License Agreement
9+
(CLA).
10+
11+
* If you are an individual writing original source code and you're sure you
12+
own the intellectual property, then you'll need to sign an [individual CLA]
13+
(https://developers.google.com/open-source/cla/individual).
14+
* If you work for a company that wants to allow you to contribute your work,
15+
then you'll need to sign a [corporate CLA]
16+
(https://developers.google.com/open-source/cla/corporate).
17+
18+
Follow either of the two links above to access the appropriate CLA and
19+
instructions for how to sign and return it. Once we receive it, we'll be able to
20+
accept your pull requests.
21+
22+
## Contributing A Patch
23+
24+
1. Submit an issue describing your proposed change to the repo in question.
25+
1. The repo owner will respond to your issue promptly.
26+
1. If your proposed change is accepted, and you haven't already done so, sign a
27+
Contributor License Agreement (see details above).
28+
1. Fork the desired repo, develop and test your code changes.
29+
1. Ensure that your code adheres to the existing style in the sample to which
30+
you are contributing. Refer to the
31+
[Google Cloud Platform Samples Style Guide]
32+
(https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the
33+
recommended coding standards for this organization.
34+
1. Ensure that your code has an appropriate set of unit tests which all pass.
35+
1. Submit a pull request.
36+

LICENSE

Lines changed: 653 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
Dynamic code loading sample
2+
============
3+
4+
This sample demonstrates how to load and use classes from dynamic feature modules.
5+
6+
Introduction
7+
------------
8+
Dynamic feature modules (DFMs) are modularized parts of your Android app. You will encounter them when you switch
9+
to using Android App Bundles and want to enable on-demand or conditional feature delivery.
10+
11+
You can read more about app bundles at http://g.co/androidappbundle
12+
13+
Contrary to regular library modules, DFMs depend on the base module (`com.android.application`) of your app.
14+
Because of this, classes defined in DFMs are not visible from the app module at compile time and need to be accessed
15+
through reflection.
16+
17+
This sample demonstrates three different approaches to safely access code from the feature module
18+
using at most 1 reflection call.
19+
20+
Common code is included in the `main` sourceSets, which contains the basic UI and ViewModel for this sample.
21+
This also contains the `StorageFeature` interface (defined in the `app` module),
22+
which will be implemented in the `storage` dynamic feature module.
23+
24+
The other sourceSets contain code that obtains an instance of the concrete implementation from the DFM
25+
using three approaches, split into the following product flavors:
26+
27+
`reflect/` -> uses a straight reflection call
28+
`serviceLoader/` -> uses the [ServiceLoader](https://developer.android.com/reference/java/util/ServiceLoader) mechanism
29+
`dagger/` -> uses Dagger 2, to enable easy instantiation of complex object graphs from the DFM
30+
31+
Please note that all approaches use 1 reflect call to instantiate the `StorageFeature.Provider`,
32+
although in the `serviceLoader` and `dagger` approaches they're somewhat hidden from the user.
33+
34+
Additionally, when compiling the `serviceLoader` flavor using recent versions of R8,
35+
the optimizer does away with dynamic class lookup and reflection, and the resulting DEX code uses direct class
36+
instantiation.
37+
38+
Pre-requisites
39+
--------------
40+
41+
* Android Studio 3.4
42+
* Access to Play Console in order to test the on-demand features
43+
44+
Getting Started
45+
---------------
46+
47+
Use Android Studio to open the project and check out the various Build Variants available.
48+
49+
Alternatively, build from the commandline using `./gradlew reflectDebug`
50+
or `./gradlew serviceLoaderDebug` or `./gradlew daggerDebug`
51+
52+
Support
53+
-------
54+
55+
If you've found an error in this sample, please file an issue:
56+
https://github.com/googlesamples/android-dynamic-code-loading/issues
57+
58+
Patches are encouraged, and may be submitted by forking this project and
59+
submitting a pull request through GitHub.
60+
61+
License
62+
-------
63+
64+
```
65+
Copyright 2019 Google LLC.
66+
67+
Licensed to the Apache Software Foundation (ASF) under one or more contributor
68+
license agreements. See the NOTICE file distributed with this work for
69+
additional information regarding copyright ownership. The ASF licenses this
70+
file to you under the Apache License, Version 2.0 (the "License"); you may not
71+
use this file except in compliance with the License. You may obtain a copy of
72+
the License at
73+
74+
http://www.apache.org/licenses/LICENSE-2.0
75+
76+
Unless required by applicable law or agreed to in writing, software
77+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
78+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
79+
License for the specific language governing permissions and limitations under
80+
the License.
81+
```

app/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

app/build.gradle

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
apply plugin: 'com.android.application'
2+
3+
apply plugin: 'kotlin-android'
4+
apply plugin: 'kotlin-kapt'
5+
6+
android {
7+
compileSdkVersion 28
8+
defaultConfig {
9+
applicationId "com.google.android.samples.dynamiccodeloading"
10+
minSdkVersion 21
11+
targetSdkVersion 28
12+
versionCode 33
13+
versionName "1.0.33"
14+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15+
}
16+
buildTypes {
17+
debugR8 {
18+
initWith(debug)
19+
minifyEnabled true
20+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21+
}
22+
23+
release {
24+
minifyEnabled true
25+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26+
signingConfig signingConfigs.debug
27+
}
28+
}
29+
30+
flavorDimensions "codeLoading"
31+
32+
productFlavors {
33+
reflect {
34+
dimension "codeLoading"
35+
}
36+
37+
serviceLoader {
38+
dimension "codeLoading"
39+
}
40+
41+
dagger {
42+
dimension "codeLoading"
43+
}
44+
}
45+
46+
dynamicFeatures = [":storage"]
47+
}
48+
49+
dependencies {
50+
implementation fileTree(dir: 'libs', include: ['*.jar'])
51+
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
52+
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
53+
54+
api 'androidx.appcompat:appcompat:1.0.2'
55+
implementation 'androidx.core:core-ktx:1.0.1'
56+
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
57+
58+
implementation "com.google.android.play:core:1.4.1"
59+
60+
daggerApi 'com.google.dagger:dagger:2.16'
61+
api 'com.google.dagger:dagger-android-support:2.16'
62+
63+
kapt 'com.google.dagger:dagger-compiler:2.16'
64+
65+
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
66+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha04'
67+
androidTestImplementation 'androidx.test:rules:1.2.0-alpha04'
68+
}

app/proguard-rules.pro

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
-keepattributes SourceFile,LineNumberTable
18+
#-keepattributes *Annotation*
19+
#
20+
21+
# If you keep the line number information, uncomment this to
22+
# hide the original source file name.
23+
#-renamesourcefileattribute SourceFile
24+
25+
26+
-keep class com.google.android.samples.storage.StorageFeatureImpl {
27+
com.google.android.samples.storage.StorageFeatureImpl$Provider Provider;
28+
}
29+
30+
-keep class com.google.android.samples.storage.StorageFeatureImpl$Provider {
31+
*;
32+
}
33+
34+
# There was a bug, but it's gone now. Uncomment on earlier R8 versions (?)
35+
#-keep interface com.google.android.samples.dynamiccodeloading.StorageFeature {
36+
# *;
37+
#}
38+
39+
-keep class kotlin.Metadata {
40+
*;
41+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.google.android.samples.dynamiccodeloading
18+
19+
import android.preference.PreferenceManager
20+
import androidx.test.espresso.Espresso.onView
21+
import androidx.test.espresso.action.ViewActions.click
22+
import androidx.test.espresso.assertion.ViewAssertions.matches
23+
import androidx.test.espresso.matcher.ViewMatchers.withId
24+
import androidx.test.espresso.matcher.ViewMatchers.withText
25+
import androidx.test.filters.LargeTest
26+
import androidx.test.platform.app.InstrumentationRegistry
27+
import androidx.test.rule.ActivityTestRule
28+
import org.junit.Before
29+
import org.junit.Rule
30+
import org.junit.Test
31+
32+
33+
@LargeTest
34+
class MainActivityTest {
35+
36+
@Rule
37+
@JvmField
38+
var mActivityTestRule = ActivityTestRule(MainActivity::class.java, false, false)
39+
40+
41+
/**
42+
* Clear sharedpreferences before each run
43+
*/
44+
@Before
45+
fun before() {
46+
PreferenceManager.getDefaultSharedPreferences(
47+
InstrumentationRegistry.getInstrumentation().getTargetContext().applicationContext
48+
).edit().clear().commit()
49+
}
50+
51+
/**
52+
* This is an (overly) simplified test that can be used locally with all APK splits installed.
53+
* It doesn't test Play Core APIs (on-demand delivery) since this is impossible without going through the Play Store
54+
*/
55+
@Test
56+
fun mainActivityTest() {
57+
mActivityTestRule.launchActivity(null)
58+
val appCompatButton = onView(withId(R.id.incrementButton))
59+
appCompatButton.perform(click())
60+
appCompatButton.perform(click())
61+
appCompatButton.perform(click())
62+
63+
onView(withId(R.id.saveButton)).perform(click())
64+
65+
InstrumentationRegistry.getInstrumentation().runOnMainSync(Runnable { mActivityTestRule.activity.recreate() })
66+
67+
val textView = onView(withId(R.id.counterText))
68+
textView.check(matches(withText("3")))
69+
}
70+
}

app/src/dagger/AndroidManifest.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ Copyright 2019 Google LLC
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
~
16+
-->
17+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
18+
package="com.google.android.samples.dynamiccodeloading">
19+
20+
<application
21+
tools:replace="android:name"
22+
android:name=".MyApplication"
23+
tools:ignore="AllowBackup,GoogleAppIndexingWarning"/>
24+
25+
</manifest>

0 commit comments

Comments
 (0)