├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── brainasaservice │ │ └── service │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── brainasaservice │ │ │ └── service │ │ │ ├── MainActivity.kt │ │ │ └── ServiceRegistry.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bench.png │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── brainasaservice │ └── service │ └── ExampleUnitTest.kt ├── art ├── dependency-graph.png ├── inject-ui-elements.png └── navigation-executor.png ├── build.gradle ├── core-feature ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── brainasaservice │ │ └── core │ │ └── feature │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── brainasaservice │ │ └── core │ │ └── feature │ │ ├── ServiceConfiguration.kt │ │ └── capability │ │ └── ServiceCapability.kt │ └── test │ └── java │ └── com │ └── brainasaservice │ └── core │ └── feature │ └── ExampleUnitTest.kt ├── core-other ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── brainasaservice │ │ └── core │ │ └── other │ │ └── ExampleInstrumentedTest.kt │ ├── main │ └── AndroidManifest.xml │ └── test │ └── java │ └── com │ └── brainasaservice │ └── core │ └── other │ └── ExampleUnitTest.kt ├── core-rx ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ └── AndroidManifest.xml ├── feature_dashboard ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── brainasaservice │ │ └── feature │ │ └── dashboard │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── brainasaservice │ │ │ └── feature │ │ │ └── dashboard │ │ │ ├── DashboardActivity.kt │ │ │ ├── DashboardServiceConfiguration.kt │ │ │ ├── DashboardViewCapability.kt │ │ │ └── ServiceProvider.kt │ └── res │ │ ├── drawable │ │ └── view_background.xml │ │ ├── layout │ │ ├── activity_dashboard.xml │ │ └── view_dashboard.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── brainasaservice │ └── feature │ └── dashboard │ └── ExampleUnitTest.kt ├── feature_greyscale_bitmap ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── brainasaservice │ └── feature │ └── greyscale │ ├── GreyscaleBitmapCapability.kt │ ├── GreyscaleServiceConfiguration.kt │ └── ServiceProvider.kt ├── feature_profile ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── brainasaservice │ │ └── feature │ │ └── profile │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── brainasaservice │ │ │ └── feature │ │ │ └── profile │ │ │ ├── ProfileActivity.kt │ │ │ ├── ProfileServiceConfiguration.kt │ │ │ ├── ProfileViewCapability.kt │ │ │ └── ServiceProvider.kt │ └── res │ │ ├── layout │ │ ├── activity_profile.xml │ │ └── view_profile.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── brainasaservice │ └── feature │ └── profile │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/misc.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | 17 | 18 | # Created by https://www.gitignore.io/api/linux,macos,android,windows,intellij+all,androidstudio 19 | # Edit at https://www.gitignore.io/?templates=linux,macos,android,windows,intellij+all,androidstudio 20 | 21 | ### Android ### 22 | # Built application files 23 | *.apk 24 | *.ap_ 25 | *.aab 26 | 27 | # Files for the ART/Dalvik VM 28 | *.dex 29 | 30 | # Java class files 31 | *.class 32 | 33 | # Generated files 34 | bin/ 35 | gen/ 36 | out/ 37 | 38 | # Gradle files 39 | .gradle/ 40 | build/ 41 | 42 | # Local configuration file (sdk path, etc) 43 | local.properties 44 | 45 | # Proguard folder generated by Eclipse 46 | proguard/ 47 | 48 | # Log Files 49 | *.log 50 | 51 | # Android Studio Navigation editor temp files 52 | .navigation/ 53 | 54 | # Android Studio captures folder 55 | captures/ 56 | 57 | # IntelliJ 58 | *.iml 59 | .idea/workspace.xml 60 | .idea/tasks.xml 61 | .idea/gradle.xml 62 | .idea/assetWizardSettings.xml 63 | .idea/dictionaries 64 | .idea/libraries 65 | .idea/caches 66 | # Android Studio 3 in .gitignore file. 67 | .idea/caches/build_file_checksums.ser 68 | .idea/modules.xml 69 | 70 | # Keystore files 71 | # Uncomment the following lines if you do not want to check your keystore files in. 72 | #*.jks 73 | #*.keystore 74 | 75 | # External native build folder generated in Android Studio 2.2 and later 76 | .externalNativeBuild 77 | 78 | # Google Services (e.g. APIs or Firebase) 79 | # google-services.json 80 | 81 | # Freeline 82 | freeline.py 83 | freeline/ 84 | freeline_project_description.json 85 | 86 | # fastlane 87 | fastlane/report.xml 88 | fastlane/Preview.html 89 | fastlane/screenshots 90 | fastlane/test_output 91 | fastlane/readme.md 92 | 93 | # Version control 94 | vcs.xml 95 | 96 | # lint 97 | lint/intermediates/ 98 | lint/generated/ 99 | lint/outputs/ 100 | lint/tmp/ 101 | # lint/reports/ 102 | 103 | ### Android Patch ### 104 | gen-external-apklibs 105 | output.json 106 | 107 | ### AndroidStudio ### 108 | # Covers files to be ignored for android development using Android Studio. 109 | 110 | # Built application files 111 | 112 | # Files for the ART/Dalvik VM 113 | 114 | # Java class files 115 | 116 | # Generated files 117 | 118 | # Gradle files 119 | .gradle 120 | 121 | # Signing files 122 | .signing/ 123 | 124 | # Local configuration file (sdk path, etc) 125 | 126 | # Proguard folder generated by Eclipse 127 | 128 | # Log Files 129 | 130 | # Android Studio 131 | /*/build/ 132 | /*/local.properties 133 | /*/out 134 | /*/*/build 135 | /*/*/production 136 | *.ipr 137 | *~ 138 | *.swp 139 | 140 | # Android Patch 141 | 142 | # External native build folder generated in Android Studio 2.2 and later 143 | 144 | # NDK 145 | obj/ 146 | 147 | # IntelliJ IDEA 148 | *.iws 149 | /out/ 150 | 151 | # User-specific configurations 152 | .idea/caches/ 153 | .idea/libraries/ 154 | .idea/shelf/ 155 | .idea/.name 156 | .idea/compiler.xml 157 | .idea/copyright/profiles_settings.xml 158 | .idea/encodings.xml 159 | .idea/misc.xml 160 | .idea/scopes/scope_settings.xml 161 | .idea/vcs.xml 162 | .idea/jsLibraryMappings.xml 163 | .idea/datasources.xml 164 | .idea/dataSources.ids 165 | .idea/sqlDataSources.xml 166 | .idea/dynamic.xml 167 | .idea/uiDesigner.xml 168 | 169 | # OS-specific files 170 | .DS_Store 171 | .DS_Store? 172 | ._* 173 | .Spotlight-V100 174 | .Trashes 175 | ehthumbs.db 176 | Thumbs.db 177 | 178 | # Legacy Eclipse project files 179 | .classpath 180 | .project 181 | .cproject 182 | .settings/ 183 | 184 | # Mobile Tools for Java (J2ME) 185 | .mtj.tmp/ 186 | 187 | # Package Files # 188 | *.war 189 | *.ear 190 | 191 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 192 | hs_err_pid* 193 | 194 | ## Plugin-specific files: 195 | 196 | # mpeltonen/sbt-idea plugin 197 | .idea_modules/ 198 | 199 | # JIRA plugin 200 | atlassian-ide-plugin.xml 201 | 202 | # Mongo Explorer plugin 203 | .idea/mongoSettings.xml 204 | 205 | # Crashlytics plugin (for Android Studio and IntelliJ) 206 | com_crashlytics_export_strings.xml 207 | crashlytics.properties 208 | crashlytics-build.properties 209 | fabric.properties 210 | 211 | ### AndroidStudio Patch ### 212 | 213 | !/gradle/wrapper/gradle-wrapper.jar 214 | 215 | ### Intellij+all ### 216 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 217 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 218 | 219 | # User-specific stuff 220 | .idea/**/workspace.xml 221 | .idea/**/tasks.xml 222 | .idea/**/usage.statistics.xml 223 | .idea/**/dictionaries 224 | .idea/**/shelf 225 | 226 | # Generated files 227 | .idea/**/contentModel.xml 228 | 229 | # Sensitive or high-churn files 230 | .idea/**/dataSources/ 231 | .idea/**/dataSources.ids 232 | .idea/**/dataSources.local.xml 233 | .idea/**/sqlDataSources.xml 234 | .idea/**/dynamic.xml 235 | .idea/**/uiDesigner.xml 236 | .idea/**/dbnavigator.xml 237 | 238 | # Gradle 239 | .idea/**/gradle.xml 240 | .idea/**/libraries 241 | 242 | # Gradle and Maven with auto-import 243 | # When using Gradle or Maven with auto-import, you should exclude module files, 244 | # since they will be recreated, and may cause churn. Uncomment if using 245 | # auto-import. 246 | # .idea/modules.xml 247 | # .idea/*.iml 248 | # .idea/modules 249 | 250 | # CMake 251 | cmake-build-*/ 252 | 253 | # Mongo Explorer plugin 254 | .idea/**/mongoSettings.xml 255 | 256 | # File-based project format 257 | 258 | # IntelliJ 259 | 260 | # mpeltonen/sbt-idea plugin 261 | 262 | # JIRA plugin 263 | 264 | # Cursive Clojure plugin 265 | .idea/replstate.xml 266 | 267 | # Crashlytics plugin (for Android Studio and IntelliJ) 268 | 269 | # Editor-based Rest Client 270 | .idea/httpRequests 271 | 272 | # Android studio 3.1+ serialized cache file 273 | 274 | # JetBrains templates 275 | **___jb_tmp___ 276 | 277 | ### Intellij+all Patch ### 278 | # Ignores the whole .idea folder and all .iml files 279 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 280 | 281 | .idea/ 282 | 283 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 284 | 285 | modules.xml 286 | 287 | # Sonarlint plugin 288 | .idea/sonarlint 289 | 290 | ### Linux ### 291 | 292 | # temporary files which can be created if a process still has a handle open of a deleted file 293 | .fuse_hidden* 294 | 295 | # KDE directory preferences 296 | .directory 297 | 298 | # Linux trash folder which might appear on any partition or disk 299 | .Trash-* 300 | 301 | # .nfs files are created when an open file is removed but is still being accessed 302 | .nfs* 303 | 304 | ### macOS ### 305 | # General 306 | .AppleDouble 307 | .LSOverride 308 | 309 | # Icon must end with two \r 310 | Icon 311 | 312 | # Thumbnails 313 | 314 | # Files that might appear in the root of a volume 315 | .DocumentRevisions-V100 316 | .fseventsd 317 | .TemporaryItems 318 | .VolumeIcon.icns 319 | .com.apple.timemachine.donotpresent 320 | 321 | # Directories potentially created on remote AFP share 322 | .AppleDB 323 | .AppleDesktop 324 | Network Trash Folder 325 | Temporary Items 326 | .apdisk 327 | 328 | ### Windows ### 329 | # Windows thumbnail cache files 330 | ehthumbs_vista.db 331 | 332 | # Dump file 333 | *.stackdump 334 | 335 | # Folder config file 336 | [Dd]esktop.ini 337 | 338 | # Recycle Bin used on file shares 339 | $RECYCLE.BIN/ 340 | 341 | # Windows Installer files 342 | *.cab 343 | *.msi 344 | *.msix 345 | *.msm 346 | *.msp 347 | 348 | # Windows shortcuts 349 | *.lnk 350 | 351 | # End of https://www.gitignore.io/api/linux,macos,android,windows,intellij+all,androidstudio 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Service Oriented Application 2 | 3 | This is an example project for `dynamic features` in a service-oriented 4 | approach. There are multiple core modules which are included through 5 | the `app` module. Also there are multiple feature modules, which 6 | depend on the `app` module as satellites. 7 | 8 | The `app` module doesn't know anything about the specific features. 9 | Instead, the `core-feature` module exposes certain base capabilities 10 | that define for example possible entry points into features. 11 | 12 | The goal of this approach is to explore a service-oriented architecture 13 | for Android applications. Each service should expose a certain API - 14 | in this approach available as a set of `ServiceCapability` objects - 15 | which allows the application to make use of the service. 16 | 17 | # Dependency Graph 18 | 19 | ![dependency graph](/art/dependency-graph.png?raw=true "Dependency Graph") 20 | 21 | # Service Configuration 22 | 23 | The service object is kept minimalistic at the moment. It only consists 24 | of a `String` representing the name and a `List` to 25 | describe what the service offers. 26 | 27 | ``` 28 | interface ServiceConfiguration { 29 | val name: String 30 | val capabilities: List 31 | } 32 | ``` 33 | 34 | Each of these `ServiceCapability` objects defines a certain entry- 35 | point into the service. 36 | 37 | ``` 38 | interface ServiceCapability 39 | ``` 40 | 41 | Capabilities can provide UI elements like `Activity`, `Fragment` or 42 | simply `View` objects - or other Android related objects like a 43 | (implicit) `BroadcastReceiver`. 44 | 45 | ![inject ui elements](/art/inject-ui-elements.png?raw=true "Inject UI Elements") 46 | 47 | Outside of the UI and Android sphere, they can simply serve as 48 | processors for data, access databases or webservices or execute 49 | other tasks. 50 | 51 | ## Capabilities 52 | 53 | In the sample application there are currently three different 54 | implementations of the `ServiceCapability` interface to showcase 55 | different use-cases. Of course the examples don't show every possible 56 | use-case, but rather a small subset. 57 | 58 | `DashboardViewCapability` 59 | - 60 | Inflates and returns a custom layout that includes a button. Clicking 61 | this button starts the `DashboardActivity` to guide the user into the 62 | service / feature. 63 | 64 | `ProfileViewCapability` 65 | - 66 | Inflates and returns a Button. This capability also adds a 67 | `View.OnAttachStateChangeListener` to the returned `View` 68 | allowing self-contained managing of for example `Animations`. 69 | 70 | When clicking the button, the `ProfileActivity` will be started. 71 | 72 | `ImageGreyscaleCapability` 73 | - 74 | This capability extends `ImageProcessorCapability` which provides a 75 | function with the signature 76 | ``` 77 | process(bitmap: Bitmap): Bitmap 78 | ``` 79 | 80 | This capability reduces the image saturation to 0, creating a greyscale 81 | effect. It's an example of a service that doesn't provide, expose or 82 | inject any UI elements but instead simply offers a service. 83 | 84 | # Service Registry 85 | 86 | The `app` module acts as the host in this scenario. Each service has 87 | a dependency on the `app` module and exposes its configuration to 88 | the `ServiceRegistry` whenever the service is available. 89 | 90 | ``` 91 | interface ServiceRegistry { 92 | fun register(service: ServiceConfiguration) 93 | fun getList(): List 94 | } 95 | ``` 96 | 97 | At the moment (since it's basically just a proof of concept) the 98 | registry is kept as simple as possible. The implementation simply 99 | keeps reference of the list and returns it, as well as offers a static 100 | instance of the registry. 101 | 102 | ## How Services are registered 103 | 104 | Since the services can be loaded during runtime - i.e. they are unknown 105 | to the `app` module at compile time - they can not be referenced in a 106 | traditional way. 107 | 108 | To ensure that each service is created and injected into the 109 | `ServiceRegistry` as soon as possible, a `ContentProvider` is created 110 | in each service that does exactly this: 111 | 112 | ``` 113 | class ServiceProvider : ContentProvider() { 114 | override fun onCreate(): Boolean { 115 | ServiceRegistry.getInstance().register(DashboardServiceConfiguration()) 116 | return true 117 | } 118 | } 119 | ``` 120 | 121 | The `ContentProvider` is added to the `AndroidManifest.xml` file, 122 | which will be merged into the final `AndroidManifest.xml` once the 123 | module has been loaded. This ensures a seamless and early instantiation. 124 | 125 | [(This is also how Firebase is initialized on Android)](https://firebase.googleblog.com/2016/12/how-does-firebase-initialize-on-android.html) 126 | 127 | # How to extend 128 | 129 | To extend the sample project, simply create a new (dynamic-) feature 130 | module. Create a `ContentProvider` (see: `ServiceProvider.kt`), add 131 | it to the `AndroidManifest.xml` of your new module (and make sure 132 | that the `authority` is unique) and inject the configuration 133 | of your newly created service into the `ServiceRegistry`. Done. 134 | 135 | If you extended existing `ServiceCapability` interfaces, they 136 | will automatically be used. If you created new interfaces, make sure 137 | you initialized them in the `core-feature` module. You also 138 | have to find the right spot in the `app` module to utilize them. 139 | 140 | # Todo 141 | 142 | Add an example that uses [Deep Linking](https://blog.usejournal.com/navigation-in-modular-applications-with-deep-linking-6a599c11e487) 143 | to facilitate the navigation. Allow services to register themselves 144 | for certain scopes in the application's URI scheme. 145 | 146 | ![deep linking](/art/navigation-executor.png?raw=true "Deep Linking") 147 | 148 | Build a more complex application with this approach: 149 | - Allow services to inject items into a bottom navigation bar 150 | - Allow services to provide renderers for certain content types in a `RecyclerView` 151 | - Real-life scenario with navigation, backstack, passing data between services 152 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.brainasaservice.service" 11 | minSdkVersion 19 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | dynamicFeatures = [":feature_profile", ":feature_dashboard", ":feature_greyscale_bitmap"] 25 | 26 | 27 | } 28 | 29 | dependencies { 30 | api project(':core-feature') 31 | api project(':core-rx') 32 | 33 | implementation project(':core-other') 34 | 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | implementation 'androidx.appcompat:appcompat:1.0.2' 37 | implementation 'androidx.core:core-ktx:1.0.1' 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 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 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/brainasaservice/service/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.service 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.brainasaservice.service", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/brainasaservice/service/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.service 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.widget.ImageView 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.core.content.ContextCompat 8 | import androidx.core.graphics.drawable.toBitmap 9 | import com.brainasaservice.core.feature.capability.ImageProcessorCapability 10 | import com.brainasaservice.core.feature.capability.ViewCapability 11 | import com.brainasaservice.core.feature.getCapabilities 12 | import kotlinx.android.synthetic.main.activity_main.contentLayout 13 | 14 | class MainActivity : AppCompatActivity() { 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_main) 19 | 20 | val serviceRegistry = ServiceRegistry.getInstance() 21 | 22 | val drawable = ContextCompat.getDrawable(this, R.drawable.bench) 23 | var bitmap = drawable?.toBitmap() 24 | 25 | val imageView = ImageView(this).apply { 26 | setImageBitmap(bitmap) 27 | } 28 | 29 | var index = 1 30 | 31 | serviceRegistry.getList().forEach { feature -> 32 | feature.getCapabilities().forEach { capability -> 33 | contentLayout.addView(capability.inflate(this, contentLayout), index++) 34 | } 35 | } 36 | 37 | contentLayout.addView(imageView, index) 38 | 39 | serviceRegistry.getList().filter { conf -> conf.capabilities.any { it is ImageProcessorCapability } } 40 | .forEach { 41 | it.getCapabilities().forEach { imageProcessorCapability -> 42 | bitmap?.let { bmp -> 43 | bitmap = imageProcessorCapability.process(bmp) 44 | } 45 | } 46 | } 47 | 48 | Handler().postDelayed({ 49 | imageView.setImageBitmap(bitmap) 50 | }, 3000) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/brainasaservice/service/ServiceRegistry.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.service 2 | 3 | import android.util.Log 4 | import com.brainasaservice.core.feature.ServiceConfiguration 5 | 6 | interface ServiceRegistry { 7 | fun register(service: ServiceConfiguration) 8 | 9 | fun getList(): List 10 | 11 | companion object { 12 | private val _instance by lazy { ServiceRegistryImpl() } 13 | 14 | fun getInstance(): ServiceRegistry = _instance 15 | } 16 | } 17 | 18 | class ServiceRegistryImpl internal constructor() : ServiceRegistry { 19 | private val list: MutableList = mutableListOf() 20 | 21 | override fun getList(): List = list 22 | 23 | override fun register(service: ServiceConfiguration) { 24 | Log.i("ServiceRegistry", "register(service=$service)") 25 | list.add(service) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/drawable/bench.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | service based app 3 | Greyscale 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/brainasaservice/service/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.service 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /art/dependency-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/art/dependency-graph.png -------------------------------------------------------------------------------- /art/inject-ui-elements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/art/inject-ui-elements.png -------------------------------------------------------------------------------- /art/navigation-executor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damian-burke/android-dynamic-feature/76165a5d592f65c057a427546507ae44cd6b03ae/art/navigation-executor.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.31' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0-alpha13' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /core-feature/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core-feature/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | implementation 'androidx.core:core-ktx:1.0.1' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 34 | } 35 | -------------------------------------------------------------------------------- /core-feature/proguard-rules.pro: -------------------------------------------------------------------------------- 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 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /core-feature/src/androidTest/java/com/brainasaservice/core/feature/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.core.feature 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.brainasaservice.core.feature.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core-feature/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core-feature/src/main/java/com/brainasaservice/core/feature/ServiceConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.core.feature 2 | 3 | import com.brainasaservice.core.feature.capability.ServiceCapability 4 | 5 | interface ServiceConfiguration { 6 | val name: String 7 | 8 | val capabilities: List 9 | } 10 | 11 | inline fun ServiceConfiguration.getCapabilities(): List { 12 | return capabilities.filter { it is T }.map { it as T } 13 | } 14 | -------------------------------------------------------------------------------- /core-feature/src/main/java/com/brainasaservice/core/feature/capability/ServiceCapability.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.core.feature.capability 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.view.View 6 | import android.view.ViewGroup 7 | 8 | interface ServiceCapability 9 | 10 | /** 11 | * Simple capability that allows the service to inflate and return a View, ready to be used 12 | * within the host. 13 | */ 14 | interface ViewCapability : ServiceCapability { 15 | fun inflate(context: Context, parent: ViewGroup?): View 16 | } 17 | 18 | /** 19 | * Capability that enables the service to perform certain actions on Bitmaps. 20 | */ 21 | interface ImageProcessorCapability : ServiceCapability { 22 | fun process(bitmap: Bitmap): Bitmap 23 | } 24 | -------------------------------------------------------------------------------- /core-feature/src/test/java/com/brainasaservice/core/feature/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.core.feature 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core-other/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core-other/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'androidx.appcompat:appcompat:1.0.2' 29 | implementation 'androidx.core:core-ktx:1.0.1' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 33 | } 34 | -------------------------------------------------------------------------------- /core-other/proguard-rules.pro: -------------------------------------------------------------------------------- 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 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /core-other/src/androidTest/java/com/brainasaservice/core/other/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.core.other 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.brainasaservice.core.other.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core-other/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /core-other/src/test/java/com/brainasaservice/core/other/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.core.other 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core-rx/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core-rx/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'androidx.appcompat:appcompat:1.0.2' 29 | implementation 'androidx.core:core-ktx:1.0.1' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 33 | } 34 | -------------------------------------------------------------------------------- /core-rx/proguard-rules.pro: -------------------------------------------------------------------------------- 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 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /core-rx/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /feature_dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /feature_dashboard/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.dynamic-feature' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | minSdkVersion 19 8 | targetSdkVersion 28 9 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation project(":app") 15 | 16 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 17 | implementation 'androidx.appcompat:appcompat:1.0.2' 18 | implementation 'androidx.core:core-ktx:1.0.1' 19 | } 20 | -------------------------------------------------------------------------------- /feature_dashboard/proguard-rules.pro: -------------------------------------------------------------------------------- 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 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /feature_dashboard/src/androidTest/java/com/brainasaservice/feature/dashboard/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.feature.dashboard 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.brainasaservice.feature.dashboard.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/java/com/brainasaservice/feature/dashboard/DashboardActivity.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.feature.dashboard 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | 6 | class DashboardActivity: AppCompatActivity() { 7 | override fun onCreate(savedInstanceState: Bundle?) { 8 | super.onCreate(savedInstanceState) 9 | setContentView(R.layout.activity_dashboard) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/java/com/brainasaservice/feature/dashboard/DashboardServiceConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.feature.dashboard 2 | 3 | import android.util.Log 4 | import com.brainasaservice.core.feature.ServiceConfiguration 5 | import com.brainasaservice.core.feature.capability.ServiceCapability 6 | 7 | class DashboardServiceConfiguration : ServiceConfiguration { 8 | override val name: String = "Dashboard" 9 | 10 | override val capabilities: List = listOf( 11 | DashboardViewCapability().also { Log.i("Dashboard", "Adding DashboardViewCapability") } 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/java/com/brainasaservice/feature/dashboard/DashboardViewCapability.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.feature.dashboard 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.brainasaservice.core.feature.capability.ViewCapability 9 | import kotlinx.android.synthetic.main.view_dashboard.view.button 10 | 11 | class DashboardViewCapability : ViewCapability { 12 | override fun inflate(context: Context, parent: ViewGroup?): View { 13 | return LayoutInflater.from(context) 14 | .inflate(R.layout.view_dashboard, parent, false).apply { 15 | button.setOnClickListener { 16 | val intent = Intent(context, DashboardActivity::class.java) 17 | context.startActivity(intent) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/java/com/brainasaservice/feature/dashboard/ServiceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.brainasaservice.feature.dashboard 2 | 3 | import android.content.ContentProvider 4 | import android.content.ContentValues 5 | import android.database.Cursor 6 | import android.net.Uri 7 | import android.util.Log 8 | import com.brainasaservice.service.ServiceRegistry 9 | 10 | class ServiceProvider : ContentProvider() { 11 | override fun insert(p0: Uri, p1: ContentValues?): Uri? = null 12 | 13 | override fun query(p0: Uri, p1: Array?, p2: String?, p3: Array?, p4: String?): Cursor? = null 14 | 15 | override fun onCreate(): Boolean { 16 | Log.i("Service", "Initializing...") 17 | ServiceRegistry.getInstance().register(DashboardServiceConfiguration()) 18 | return true 19 | } 20 | 21 | override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?): Int = 0 22 | 23 | override fun delete(p0: Uri, p1: String?, p2: Array?): Int = 0 24 | 25 | override fun getType(p0: Uri): String? = null 26 | } 27 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/res/drawable/view_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/res/layout/activity_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /feature_dashboard/src/main/res/layout/view_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |