├── .gitattributes
├── .gitignore
├── .idea
└── .gitignore
├── README.md
├── README_cn.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── andy
│ │ └── modules
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── andy
│ │ │ └── modules
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainActivityDelegate.kt
│ │ │ ├── MainComponentFactory.kt
│ │ │ ├── component
│ │ │ └── NoneUiComponent.kt
│ │ │ └── ui
│ │ │ ├── BottomBar.kt
│ │ │ ├── ContentLayout.kt
│ │ │ └── TitleBar.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── layout_bottom_bar.xml
│ │ ├── layout_content_main.xml
│ │ └── layout_title_bar.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── navigation
│ │ └── nav_graph.xml
│ │ ├── values-land
│ │ └── dimens.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values-v23
│ │ └── themes.xml
│ │ ├── values-w1240dp
│ │ └── dimens.xml
│ │ ├── values-w600dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── andy
│ └── modules
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── libs
└── modularization
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── andy
│ │ └── modularization
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── andy
│ │ └── modularization
│ │ ├── ActivityContainer.kt
│ │ ├── ActivityLifeCycle.kt
│ │ ├── Component.kt
│ │ ├── Container.kt
│ │ ├── HolderIdProvider.kt
│ │ ├── Service.kt
│ │ ├── ServiceManager.kt
│ │ └── StateHandle.kt
│ └── test
│ └── java
│ └── com
│ └── andy
│ └── modularization
│ └── ExampleUnitTest.kt
└── settings.gradle.kts
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | /.idea
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Modularization Architecture Example
2 |
3 | 
4 |
5 | [简体中文版说明 >>>](https://github.com/andyhaha/modules/blob/main/README_cn.md)
6 |
7 | ## Project Overview
8 |
9 | This project demonstrates a modularization architecture based on Kotlin and Android. By using modularization, we can split an app into multiple independent modules, each of which can either be UI-related or non-UI-related, depending on the requirements. Components communicate via Services, achieving decoupling and increasing code reusability.
10 |
11 | This architecture supports dynamic loading and unloading of components across different Activities, while also allowing communication between components via Services for flexible management and extension.
12 |
13 | ## Features
14 |
15 | - **UI Modularization**: Each component can be related to UI, such as `TitleBar`, `Content`, and `BottomBar`, or non-UI functionality.
16 | - **Service Communication**: Components communicate via Services, achieving decoupling.
17 | - **Dynamic Loading & Management**: Components can be dynamically loaded, initialized, and destroyed, with management via a unified factory.
18 | - **Activity Lifecycle Management**: Components manage their lifecycle based on the Activity lifecycle to avoid memory leaks and resource wastage.
19 | ## Architecture Design
20 |
21 | ### Core of Modularization Architecture
22 |
23 | - **Component Class**: Each component extends the `Component` class, which manages the lifecycle and binds the Activity and View.
24 | - **Factory Class**: The `ComponentFactory` class is used to create and manage all component instances, providing a unified interface for retrieving, registering, and destroying components.
25 | - **Service Manager**: The `ServiceManager` class provides a mechanism for communication between components using Services.
26 | - **MainActivityDelegate**: The `MainActivityDelegate` class manages the creation, lifecycle, and destruction of components via delegation.
27 |
28 | ## How to Use
29 |
30 | 1. **Add Modules**: To add a new module, create a new class extending `Component` in the `modules/` directory.
31 | 2. **Register Components**: Use the `ComponentFactory` to register and manage your components.
32 | 3. **Service Communication**: To enable communication between components, use `ServiceManager` to send and receive messages.
33 | 4. **Activity Integration**: Ensure that your components are integrated into the activity lifecycle through `MainActivityDelegate`.
34 |
35 | ## License
36 |
37 | Copyright (c) [2024] [Andy]
38 |
39 | Permission is hereby granted, free of charge, to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software, and to permit others to do so, subject to the following conditions:
40 |
41 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the software.
42 |
43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 |
--------------------------------------------------------------------------------
/README_cn.md:
--------------------------------------------------------------------------------
1 | # 模块化架构示例
2 |
3 | ## 项目概述
4 |
5 | 本项目展示了基于 Kotlin 和 Android 的模块化架构。通过使用模块化,我们可以将一个应用拆分成多个独立的模块,每个模块可以是与 UI 相关的或与非 UI 功能相关的,具体取决于需求。组件通过服务进行通信,实现解耦和提高代码复用性。
6 |
7 | 该架构支持不同 Activity 中组件的动态加载和卸载,同时允许组件之间通过服务进行通信,灵活管理和扩展。
8 |
9 | ## 特性
10 |
11 | - **模块化**:每个组件可以是与 UI 相关的,例如 `TitleBar`、`Content` 和 `BottomBar`,也可以是非 UI 功能。
12 | - **服务通信**:组件之间通过服务进行通信,实现解耦。
13 | - **动态加载与管理**:组件可以动态加载、初始化和销毁,并通过统一的工厂进行管理。
14 | - **Activity 生命周期管理**:组件根据 Activity 生命周期来管理自身的生命周期,避免内存泄漏和资源浪费。
15 |
16 | ## 架构设计
17 |
18 | ### 模块化架构的核心
19 |
20 | - **组件类**:每个组件继承自 `Component` 类,该类管理组件的生命周期,并绑定 Activity 和 View。
21 | - **工厂类**:`ComponentFactory` 类用于创建和管理所有组件实例,提供统一的接口用于获取、注册和销毁组件。
22 | - **服务管理器**:`ServiceManager` 类提供组件之间通过服务进行通信的机制。
23 | - **MainActivityDelegate**:`MainActivityDelegate` 类通过委托方式管理组件的创建、生命周期和销毁。
24 |
25 | ## 使用方式
26 |
27 | 1. **添加模块**:要添加一个新模块,请在 `modules/` 目录中创建一个继承自 `Component` 的新类。
28 | 2. **注册组件**:使用 `ComponentFactory` 来注册和管理您的组件。
29 | 3. **服务通信**:要启用组件之间的通信,使用 `ServiceManager` 发送和接收消息。
30 | 4. **集成 Activity**:确保您的组件通过 `MainActivityDelegate` 集成到 Activity 生命周期中。
31 |
32 | ## 许可证
33 |
34 | 版权所有 (c) [2024] [Andy]
35 |
36 | 特此免费授予使用、复制、修改、合并、发布、分发、再许可和/或出售本软件的副本,以及允许他人这样做的权限,但须遵守以下条件:
37 |
38 | 上述版权声明和本许可声明应包含在本软件的所有副本或重要部分中。
39 |
40 | 本软件按“原样”提供,不附任何明示或暗示的担保,包括但不限于适销性、特定用途的适用性和非侵权的担保。在任何情况下,作者或版权持有者均不对因使用本软件或与本软件有关的行为所引起的任何索赔、损害或其他责任负责。
41 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "com.andy.modules"
8 | compileSdk = 35
9 |
10 | defaultConfig {
11 | applicationId = "com.andy.modules"
12 | minSdk = 23
13 | targetSdk = 35
14 | versionCode = 1
15 | versionName = "1.0"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | isMinifyEnabled = false
23 | proguardFiles(
24 | getDefaultProguardFile("proguard-android-optimize.txt"),
25 | "proguard-rules.pro"
26 | )
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_11
31 | targetCompatibility = JavaVersion.VERSION_11
32 | }
33 | kotlinOptions {
34 | jvmTarget = "11"
35 | }
36 | buildFeatures {
37 | viewBinding = true
38 | }
39 | }
40 |
41 | dependencies {
42 |
43 | implementation(libs.androidx.core.ktx)
44 | implementation(libs.androidx.appcompat)
45 | implementation(libs.material)
46 | implementation(libs.androidx.constraintlayout)
47 | implementation(libs.androidx.navigation.fragment.ktx)
48 | implementation(libs.androidx.navigation.ui.ktx)
49 | testImplementation(libs.junit)
50 | androidTestImplementation(libs.androidx.junit)
51 | androidTestImplementation(libs.androidx.espresso.core)
52 |
53 | implementation(project(":libs:modularization"))
54 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/andy/modules/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules
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.andy.modules", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.andy.modules.databinding.ActivityMainBinding
6 |
7 | class MainActivity : AppCompatActivity() {
8 |
9 | private lateinit var binding: ActivityMainBinding
10 | private var mainActivityDelegate: MainActivityDelegate? = null
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | binding = ActivityMainBinding.inflate(layoutInflater)
15 | setContentView(binding.root)
16 | mainActivityDelegate = MainActivityDelegate(
17 | activity = this,
18 | binding = binding
19 | )
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/MainActivityDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules
2 |
3 | import androidx.lifecycle.Lifecycle
4 | import androidx.lifecycle.LifecycleEventObserver
5 | import androidx.lifecycle.LifecycleOwner
6 | import com.andy.modularization.ActivityContainer
7 | import com.andy.modularization.ActivityLifeCycle
8 | import com.andy.modules.databinding.ActivityMainBinding
9 | import java.lang.ref.WeakReference
10 |
11 | class MainActivityDelegate(
12 | private val activity: MainActivity,
13 | private val binding: ActivityMainBinding,
14 | ) : ActivityLifeCycle {
15 | init {
16 | activity.lifecycle.addObserver(InnerLifecycleObserver(this))
17 | }
18 |
19 | private val roomContainer by lazy(LazyThreadSafetyMode.NONE) {
20 | ActivityContainer(activity)
21 | }
22 |
23 | private val componentFactory by lazy(LazyThreadSafetyMode.NONE) {
24 | MainComponentFactory(activity, binding)
25 | }
26 |
27 | override fun onCreate() {
28 | roomContainer.onCreate()
29 | componentFactory.newComponents()
30 | componentFactory.components().forEach {
31 | roomContainer.addComponent(it)
32 | }
33 | }
34 |
35 | override fun onStart() {
36 | roomContainer.onStart()
37 | }
38 |
39 | override fun onResume() {
40 | roomContainer.onResume()
41 | }
42 |
43 | override fun onPause() {
44 | roomContainer.onPause()
45 | }
46 |
47 | override fun onStop() {
48 | roomContainer.onStop()
49 | }
50 |
51 | override fun onDestroy() {
52 | roomContainer.onDestroy()
53 | componentFactory.release()
54 | }
55 |
56 | class InnerLifecycleObserver(delegate: MainActivityDelegate) : LifecycleEventObserver {
57 | private val activityDelegate = WeakReference(delegate)
58 |
59 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
60 | when (event) {
61 | Lifecycle.Event.ON_CREATE -> {
62 | activityDelegate.get()?.onCreate()
63 | }
64 | Lifecycle.Event.ON_START -> {
65 | activityDelegate.get()?.onStart()
66 | }
67 | Lifecycle.Event.ON_RESUME -> {
68 | activityDelegate.get()?.onResume()
69 | }
70 | Lifecycle.Event.ON_PAUSE -> {
71 | activityDelegate.get()?.onPause()
72 | }
73 | Lifecycle.Event.ON_STOP -> {
74 | activityDelegate.get()?.onStop()
75 | }
76 | Lifecycle.Event.ON_DESTROY -> {
77 | activityDelegate.get()?.onDestroy()
78 | }
79 | else -> {
80 | // Handle other lifecycle events here if needed
81 | }
82 | }
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/MainComponentFactory.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import com.andy.modularization.Component
5 | import com.andy.modularization.Service
6 | import com.andy.modularization.ServiceManager
7 | import com.andy.modularization.holderId
8 | import com.andy.modules.component.NoneUiComponent
9 | import com.andy.modules.databinding.ActivityMainBinding
10 | import com.andy.modules.ui.BottomBar
11 | import com.andy.modules.ui.ContentLayout
12 | import com.andy.modules.ui.TitleBar
13 |
14 | class MainComponentFactory(
15 | private val activity: AppCompatActivity,
16 | private val binding: ActivityMainBinding,
17 | ) : Component.ComponentFactory {
18 | private val components = mutableListOf()
19 |
20 | override fun newComponents() {
21 | addComponent(TitleBar(activity, binding.titleBarContent))
22 | addComponent(ContentLayout(activity, binding.layoutContent))
23 | addComponent(BottomBar(activity, binding.bottomBarContent))
24 | addComponent(NoneUiComponent(activity))
25 | }
26 |
27 | private fun addComponent(component: Component) {
28 | components.add(component)
29 | if (component is Service) {
30 | ServiceManager.registerService(component.holderId, component)
31 | }
32 | }
33 |
34 | override fun components(): List {
35 | return components
36 | }
37 |
38 | override fun release() {
39 | components.forEach {
40 | ServiceManager.releaseHolderServices(it.holderId)
41 | }
42 | components.clear()
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/component/NoneUiComponent.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules.component
2 |
3 | import android.util.Log
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.andy.modularization.Component
6 |
7 | /**
8 | * A component that is unrelated to the UI.
9 | * It handles non-visual logic or background tasks.
10 | */
11 | class NoneUiComponent(
12 | override val activity: AppCompatActivity
13 | ) : Component() {
14 |
15 | companion object {
16 | private const val TAG = "NoneUiComponent"
17 | }
18 |
19 | override fun onCreate() {
20 | Log.d(TAG, "onCreate: Initializing NoneUiComponent")
21 | }
22 |
23 | override fun onStart() {
24 | Log.d(TAG, "onStart: NoneUiComponent started")
25 | }
26 |
27 | override fun onResume() {
28 | Log.d(TAG, "onResume: NoneUiComponent resumed")
29 | }
30 |
31 | override fun onPause() {
32 | Log.d(TAG, "onPause: NoneUiComponent paused")
33 | }
34 |
35 | override fun onStop() {
36 | Log.d(TAG, "onStop: NoneUiComponent stopped")
37 | }
38 |
39 | override fun onDestroy() {
40 | Log.d(TAG, "onDestroy: NoneUiComponent destroyed")
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/ui/BottomBar.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules.ui
2 |
3 | import android.util.Log
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.andy.modularization.Component
6 | import com.andy.modularization.Service
7 | import com.andy.modularization.getString
8 | import com.andy.modules.R
9 | import com.andy.modules.databinding.LayoutBottomBarBinding
10 |
11 | class BottomBar(
12 | override val activity: AppCompatActivity,
13 | private val binding: LayoutBottomBarBinding,
14 | ) : Component(), BottomBarService {
15 |
16 | companion object {
17 | private const val TAG = "BottomBar"
18 | }
19 |
20 | override fun onCreate() {
21 | Log.d(TAG, "onCreate: Initializing BottomBar")
22 | binding.textBottom.text = getString(R.string.bottom)
23 | }
24 |
25 | override fun onStart() {
26 | Log.d(TAG, "onStart: BottomBar started")
27 | }
28 |
29 | override fun onResume() {
30 | Log.d(TAG, "onResume: BottomBar resumed")
31 | }
32 |
33 | override fun onPause() {
34 | Log.d(TAG, "onPause: BottomBar paused")
35 | }
36 |
37 | override fun onStop() {
38 | Log.d(TAG, "onStop: BottomBar stopped")
39 | }
40 |
41 | override fun onDestroy() {
42 | Log.d(TAG, "onDestroy: BottomBar destroyed")
43 | }
44 |
45 | override fun sayHello(content: String) {
46 | Log.d(TAG, "BottomBar received message: $content")
47 | }
48 | }
49 |
50 | interface BottomBarService : Service {
51 | fun sayHello(content: String)
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/ui/ContentLayout.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules.ui
2 |
3 | import android.util.Log
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.andy.modularization.Component
6 | import com.andy.modularization.getString
7 | import com.andy.modules.R
8 | import com.andy.modules.databinding.LayoutContentMainBinding
9 |
10 | class ContentLayout(
11 | override val activity: AppCompatActivity,
12 | private val binding: LayoutContentMainBinding,
13 | ) : Component() {
14 |
15 | companion object {
16 | private const val TAG = "Content"
17 | }
18 |
19 | override fun onCreate() {
20 | Log.d(TAG, "onCreate: Initializing Content component")
21 | binding.textContent.text = getString(R.string.content)
22 | }
23 |
24 | override fun onStart() {
25 | Log.d(TAG, "onStart: Content component started")
26 | }
27 |
28 | override fun onResume() {
29 | Log.d(TAG, "onResume: Content component resumed")
30 | }
31 |
32 | override fun onPause() {
33 | Log.d(TAG, "onPause: Content component paused")
34 | }
35 |
36 | override fun onStop() {
37 | Log.d(TAG, "onStop: Content component stopped")
38 | }
39 |
40 | override fun onDestroy() {
41 | Log.d(TAG, "onDestroy: Content component destroyed")
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/andy/modules/ui/TitleBar.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules.ui
2 |
3 | import android.util.Log
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.andy.modularization.Component
6 | import com.andy.modularization.ServiceManager
7 | import com.andy.modularization.getString
8 | import com.andy.modularization.holderId
9 | import com.andy.modules.R
10 | import com.andy.modules.databinding.LayoutTitleBarBinding
11 |
12 | class TitleBar(
13 | override val activity: AppCompatActivity,
14 | private val binding: LayoutTitleBarBinding,
15 | ) : Component() {
16 |
17 | companion object {
18 | private const val TAG = "TitleBar"
19 | }
20 |
21 | override fun onCreate() {
22 | Log.d(TAG, "onCreate: Initializing TitleBar component")
23 | binding.textTitle.text = getString(R.string.title)
24 | // It is important to note that the generic parameter
25 | // for ServiceManager.getService
26 | // must be the concrete class name, not the interface.
27 | // Otherwise, the corresponding service cannot be found.
28 | val service = ServiceManager.getService(holderId)
29 | service?.sayHello("Hello from TitleBar")
30 | }
31 |
32 | override fun onStart() {
33 | Log.d(TAG, "onStart: TitleBar component started")
34 | }
35 |
36 | override fun onResume() {
37 | Log.d(TAG, "onResume: TitleBar component resumed")
38 | }
39 |
40 | override fun onPause() {
41 | Log.d(TAG, "onPause: TitleBar component paused")
42 | }
43 |
44 | override fun onStop() {
45 | Log.d(TAG, "onStop: TitleBar component stopped")
46 | }
47 |
48 | override fun onDestroy() {
49 | Log.d(TAG, "onDestroy: TitleBar component destroyed")
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/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 |
10 |
11 |
17 |
18 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_bottom_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_title_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
18 |
23 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w1240dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 200dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #F5F5F5">
6 | #FF0000
7 | #B22222
8 | #F5F5DC
9 | #FF4500
10 | #1E90FF
11 | #00BFFF
12 | #32CD32
13 | #66FF66
14 | #7FFFAA
15 | #FFD700
16 | #9B59B6
17 | #FF8C00
18 | #00FFFF
19 | #FF1493
20 | #000080
21 | #ADD8E6
22 | #E6E6FA
23 | #D3D3D3
24 | #C0C0C0
25 | #A9A9A9
26 | #808080
27 | #696969
28 | #FFD700
29 | #FFF8DC
30 | #FFFAF0
31 | #FDF5E6
32 | #F5DEB3
33 | #FFE4B5
34 | #FFA500
35 | #FFEFD5"
36 | #FFEBCD">
37 | #FFDEAD">
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | modules
3 | Settings
4 |
5 | First Fragment
6 | Second Fragment
7 | Next
8 | Previous
9 | TitleBar component
10 | Content component
11 | BottomBar component
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/andy/modules/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modules
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 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | alias(libs.plugins.android.library) apply false
6 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.7.2"
3 | kotlin = "1.9.24"
4 | coreKtx = "1.15.0"
5 | junit = "4.13.2"
6 | junitVersion = "1.2.1"
7 | espressoCore = "3.6.1"
8 | appcompat = "1.7.0"
9 | material = "1.12.0"
10 | constraintlayout = "2.2.0"
11 | navigationFragmentKtx = "2.8.4"
12 | navigationUiKtx = "2.8.4"
13 |
14 | [libraries]
15 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
16 | junit = { group = "junit", name = "junit", version.ref = "junit" }
17 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
18 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
19 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
20 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
21 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
22 | androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
23 | androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
24 |
25 | [plugins]
26 | android-application = { id = "com.android.application", version.ref = "agp" }
27 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
28 | android-library = { id = "com.android.library", version.ref = "agp" }
29 |
30 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Nov 24 10:39:41 JST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/libs/modularization/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libs/modularization/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "com.andy.modularization"
8 | compileSdk = 34
9 |
10 | defaultConfig {
11 | minSdk = 23
12 |
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles("consumer-rules.pro")
15 | }
16 |
17 | buildTypes {
18 | release {
19 | isMinifyEnabled = false
20 | proguardFiles(
21 | getDefaultProguardFile("proguard-android-optimize.txt"),
22 | "proguard-rules.pro"
23 | )
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_11
28 | targetCompatibility = JavaVersion.VERSION_11
29 | }
30 | kotlinOptions {
31 | jvmTarget = "11"
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation(libs.androidx.core.ktx)
38 | implementation(libs.androidx.appcompat)
39 | implementation(libs.material)
40 | testImplementation(libs.junit)
41 | androidTestImplementation(libs.androidx.junit)
42 | androidTestImplementation(libs.androidx.espresso.core)
43 | }
--------------------------------------------------------------------------------
/libs/modularization/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyhaha/componentFramework/81f705f876afae4123e4493cac8e4cf34cb38e7d/libs/modularization/consumer-rules.pro
--------------------------------------------------------------------------------
/libs/modularization/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
--------------------------------------------------------------------------------
/libs/modularization/src/androidTest/java/com/andy/modularization/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
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.andy.modularization.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/ActivityContainer.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | import android.app.Activity
4 |
5 | /**
6 | * This class represents a container that simulates an Activity lifecycle.
7 | * It is responsible for managing components and their lifecycle, mimicking
8 | * the activity's lifecycle methods.
9 | */
10 | open class ActivityContainer(val activity: Activity) : Container {
11 | private val components = arrayListOf()
12 |
13 | /**
14 | * Reloads all components in the container by calling their performReload() method.
15 | * This simulates a refresh of the components, typically when the Activity's state
16 | * needs to be reset.
17 | */
18 | fun reload() {
19 | components.forEach {
20 | it.performReload()
21 | }
22 | }
23 |
24 | /**
25 | * Called when the container is first created.
26 | * This method can be overridden to perform initialization logic.
27 | */
28 | override fun onCreate() {
29 | // Add any logic needed when the container is created (e.g., setup)
30 | }
31 |
32 | /**
33 | * Called when the container is about to become visible, similar to an Activity's onStart().
34 | * This method resumes all components by calling their performResume() method.
35 | */
36 | override fun onStart() {
37 | components.forEach {
38 | it.performStart()
39 | }
40 | }
41 |
42 | /**
43 | * Called when the container has become visible, similar to an Activity's onResume().
44 | * This method resumes all components by calling their performResume() method.
45 | */
46 | override fun onResume() {
47 | components.forEach {
48 | it.performResume()
49 | }
50 | }
51 |
52 | /**
53 | * Called when the container is no longer visible, similar to an Activity's onPause().
54 | * This method pauses all components by calling their performPause() method.
55 | */
56 | override fun onPause() {
57 | components.forEach {
58 | it.performPause()
59 | }
60 | }
61 |
62 | /**
63 | * Called when the container is no longer visible and will be destroyed soon,
64 | * similar to an Activity's onStop().
65 | * This method stops all components by calling their performStop() method.
66 | */
67 | override fun onStop() {
68 | components.forEach {
69 | it.performStop()
70 | }
71 | }
72 |
73 | /**
74 | * Returns the list of components currently contained in this container.
75 | * This simulates the retrieval of components or fragments in an Activity.
76 | * @return the list of components
77 | */
78 | override fun components(): List {
79 | return components
80 | }
81 |
82 | /**
83 | * Adds a component to the container and calls its performCreate() method to initialize it.
84 | * This mimics the addition of a Fragment or component to an Activity.
85 | * @param component the component to be added
86 | */
87 | override fun addComponent(component: Component) {
88 | components.add(component)
89 | // Initialize the component when it is added
90 | component.performCreate()
91 | }
92 |
93 | /**
94 | * Removes a component from the container.
95 | * This simulates the removal of a Fragment or component from an Activity.
96 | * @param component the component to be removed
97 | */
98 | override fun removeComponent(component: Component) {
99 | components.remove(component)
100 | }
101 |
102 | /**
103 | * Called when the container is being destroyed, similar to an Activity's onDestroy().
104 | * Calls performDestroy() on all components and clears the component list.
105 | */
106 | override fun onDestroy() {
107 | components.forEach {
108 | it.performDestroy()
109 | }
110 | // Clean up the components when the container is destroyed
111 | components.clear()
112 | }
113 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/ActivityLifeCycle.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | /**
4 | * Interface representing the basic lifecycle methods of an Activity.
5 | * These methods mirror the lifecycle stages of an activity, from creation to destruction.
6 | */
7 | interface ActivityLifeCycle {
8 |
9 | /**
10 | * Called when the activity is first created.
11 | * This is where you should initialize your activity, set up resources, and create any necessary UI components.
12 | */
13 | fun onCreate()
14 |
15 | /**
16 | * Called when the activity is about to become visible.
17 | * This is the appropriate place to initialize things that require the activity to be visible,
18 | * such as starting animations or resuming services.
19 | */
20 | fun onStart()
21 |
22 | /**
23 | * Called when the activity has become visible and is now in the foreground (it is "resumed").
24 | * This is the place to interact with the user, start animations, or acquire resources that
25 | * should be available during the active interaction with the activity.
26 | */
27 | fun onResume()
28 |
29 | /**
30 | * Called when the activity is no longer visible because another activity is in the foreground.
31 | * This is where you should pause ongoing operations, save data, or stop any UI updates that
32 | * don't need to occur while the activity is paused.
33 | */
34 | fun onPause()
35 |
36 | /**
37 | * Called when the activity is no longer visible and will soon be destroyed.
38 | * This method allows for releasing resources or stopping processes that should not continue
39 | * once the activity is no longer visible.
40 | */
41 | fun onStop()
42 |
43 | /**
44 | * Called before the activity is destroyed.
45 | * Use this method to clean up resources, save any necessary data, and finalize operations
46 | * that require activity destruction.
47 | */
48 | fun onDestroy()
49 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/Component.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | import android.content.Context
4 | import androidx.annotation.MainThread
5 | import androidx.annotation.StringRes
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.lifecycle.LifecycleCoroutineScope
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.ViewModelLazy
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.lifecycle.coroutineScope
12 |
13 | /**
14 | * Extension function for obtaining a ViewModel from a Component.
15 | * It uses the ViewModelProvider associated with the Activity and the provided factory.
16 | */
17 | @MainThread
18 | inline fun Component.viewModels(
19 | noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
20 | ): Lazy {
21 | val factoryPromise = factoryProducer ?: {
22 | (activity as? AppCompatActivity)?.defaultViewModelProviderFactory
23 | ?: throw IllegalStateException("Activity must be AppCompatActivity")
24 | }
25 |
26 | return ViewModelLazy(VM::class, { activity.viewModelStore }, factoryPromise)
27 | }
28 |
29 | /**
30 | * Extension property to retrieve the lifecycleScope for the component's Activity.
31 | */
32 | val Component.lifecycleScope: LifecycleCoroutineScope
33 | get() = (activity as? AppCompatActivity)?.lifecycle?.coroutineScope
34 | ?: throw IllegalStateException("Activity must be AppCompatActivity")
35 |
36 | /**
37 | * Extension property to retrieve the applicationContext for the component's Activity.
38 | */
39 | val Component.applicationContext: Context
40 | get() = activity.applicationContext
41 |
42 | /**
43 | * Extension property to provide a unique holder identifier (holderId) for a Component.
44 | *
45 | * This property uses the `hashCode` of the Component instance as its unique identifier,
46 | * ensuring that each Component can generate a distinct and consistent ID.
47 | * The `holderId` is represented as a String for compatibility with service management.
48 | *
49 | * Note: Using `hashCode` ensures uniqueness per instance, but care should be taken if
50 | * components need to share the same holderId across multiple instances.
51 | */
52 | val Component.holderId: String
53 | get() = activity.hashCode().toString()
54 |
55 | fun Component.getString(@StringRes resId: Int): String {
56 | return activity.getString(resId)
57 | }
58 |
59 | /**
60 | * Abstract class that represents a component which mimics an Activity's lifecycle methods.
61 | * This class is responsible for managing a component's lifecycle and can be inherited
62 | * by other components to implement custom lifecycle behavior.
63 | */
64 | abstract class Component : ActivityLifeCycle {
65 | // The Activity associated with this component.
66 | abstract val activity: AppCompatActivity
67 |
68 | /**
69 | * This method is responsible for performing a reload of the component.
70 | * It can be overridden by subclasses to implement specific reload behavior.
71 | */
72 | fun performReload() {
73 | reload()
74 | }
75 |
76 | /**
77 | * This method simulates the onCreate() lifecycle callback for the component.
78 | * It should be called when the component is created.
79 | */
80 | fun performCreate() {
81 | onCreate()
82 | }
83 |
84 | /**
85 | * This method simulates the onStart() lifecycle callback for the component.
86 | * It should be called when the component is started.
87 | */
88 | fun performStart() {
89 | onStart()
90 | }
91 |
92 | /**
93 | * This method simulates the onResume() lifecycle callback for the component.
94 | * It should be called when the component is resumed.
95 | */
96 | fun performResume() {
97 | onResume()
98 | }
99 |
100 | /**
101 | * This method simulates the onPause() lifecycle callback for the component.
102 | * It should be called when the component is paused.
103 | */
104 | fun performPause() {
105 | onPause()
106 | }
107 |
108 | /**
109 | * This method simulates the onStop() lifecycle callback for the component.
110 | * It should be called when the component is stopped.
111 | */
112 | fun performStop() {
113 | onStop()
114 | }
115 |
116 | /**
117 | * This method is called when a component is destroyed.
118 | * It triggers the onDestroy() lifecycle callback and cleans up the component.
119 | */
120 | fun performDestroy() {
121 | onDestroy()
122 | }
123 |
124 | /**
125 | * This is a hook for subclasses to implement specific reload logic when needed.
126 | * It can be overridden by subclasses to define custom reload behavior.
127 | */
128 | protected open fun reload() {
129 | // Default reload logic can be implemented here if necessary.
130 | }
131 |
132 | // Lifecycle methods that should be overridden by subclasses to implement specific behavior.
133 |
134 | /**
135 | * Called when the component is created.
136 | * Subclasses should override this method to implement specific setup logic.
137 | */
138 | override fun onCreate() {
139 | // Implement component creation logic in subclasses.
140 | }
141 |
142 | /**
143 | * Called when the component is about to become visible.
144 | * Subclasses should override this method to implement behavior for when the component starts.
145 | */
146 | override fun onStart() {
147 | // Implement component start logic in subclasses.
148 | }
149 |
150 | /**
151 | * Called when the component becomes visible.
152 | * Subclasses should override this method to implement behavior for when the component resumes.
153 | */
154 | override fun onResume() {
155 | // Implement component resume logic in subclasses.
156 | }
157 |
158 | /**
159 | * Called when the component is no longer visible.
160 | * Subclasses should override this method to implement behavior for when the component pauses.
161 | */
162 | override fun onPause() {
163 | // Implement component pause logic in subclasses.
164 | }
165 |
166 | /**
167 | * Called when the component is no longer visible and will be destroyed soon.
168 | * Subclasses should override this method to implement behavior for when the component stops.
169 | */
170 | override fun onStop() {
171 | // Implement component stop logic in subclasses.
172 | }
173 |
174 | /**
175 | * Called before the component is destroyed.
176 | * Subclasses should override this method to implement cleanup logic for the component.
177 | */
178 | override fun onDestroy() {
179 | // Implement component destruction logic in subclasses.
180 | }
181 |
182 | /**
183 | * Factory interface for creating and managing components.
184 | * Implementations of this interface are responsible for creating new components,
185 | * retrieving a list of components, and releasing resources when no longer needed.
186 | */
187 | interface ComponentFactory {
188 | /**
189 | * Create new components.
190 | */
191 | fun newComponents()
192 |
193 | /**
194 | * Retrieve the list of components managed by the factory.
195 | * @return the list of components.
196 | */
197 | fun components(): List
198 |
199 | /**
200 | * Release any resources or cleanup the components.
201 | */
202 | fun release()
203 | }
204 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/Container.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | /**
4 | * The ComponentManager is responsible for managing the collection of components.
5 | * It allows for adding, removing, and retrieving components that are part of the system.
6 | * This interface extends ActivityLifeCycle to ensure lifecycle management of components.
7 | */
8 | interface Container : ActivityLifeCycle {
9 |
10 | /**
11 | * Retrieves the list of components currently managed by the container.
12 | * @return a list of all components added to the container
13 | */
14 | fun components(): List
15 |
16 | /**
17 | * Adds a component to the container.
18 | * @param component the component to be added
19 | */
20 | fun addComponent(component: Component)
21 |
22 | /**
23 | * Removes a component from the container.
24 | * @param component the component to be removed
25 | */
26 | fun removeComponent(component: Component)
27 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/HolderIdProvider.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | /**
4 | * Interface for providing a unique holder identifier (holderId).
5 | *
6 | * Classes implementing this interface should define a unique `holderId`,
7 | * which is typically used to associate and manage services or components
8 | * related to specific holders (e.g., Activities, Fragments, or other entities).
9 | *
10 | * This design helps isolate and manage dependencies or services
11 | * within the scope of the holder, enabling better resource control and modularity.
12 | */
13 | interface HolderIdProvider {
14 | /**
15 | * A unique identifier for the holder.
16 | *
17 | * This ID is used to register, retrieve, and manage services or other entities
18 | * associated with the holder. It must be unique within the application's context
19 | * to avoid conflicts between different holders.
20 | */
21 | val holderId: String
22 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/Service.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | /**
4 | * Interface for communication between components (Service)
5 | */
6 | interface Service
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/ServiceManager.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | import android.util.Log
4 |
5 | /**
6 | * Manages and provides component communication services for various holders (e.g., Activities, Fragments).
7 | *
8 | * This object is responsible for registering, unregistering, retrieving,
9 | * and releasing service instances associated with specific holders.
10 | * It ensures isolation between holders and prevents resource leaks
11 | * by managing services within the scope of each holder.
12 | */
13 | object ServiceManager {
14 | // Stores services for each holder.
15 | // The outer map's key is the holder ID, and the inner map stores services keyed by their class names.
16 | val holderServices = mutableMapOf>()
17 |
18 | /**
19 | * Registers a new service for a specific holder.
20 | *
21 | * @param holderId A unique identifier for the holder (e.g., Activity, Fragment).
22 | * @param service The service instance to register.
23 | */
24 | @JvmStatic
25 | fun registerService(holderId: String, service: Service) {
26 | // Get or create the service map for the holder
27 | val services = holderServices.getOrPut(holderId) { mutableMapOf() }
28 | // Register the service using its class name as the key
29 | services[service.javaClass.name] = service
30 | Log.d("ServiceManager", "registerService() service " +
31 | "for holder: $holderId, service.name: ${service.javaClass.name}")
32 | }
33 |
34 | /**
35 | * Registers a new service for a specific holder.
36 | *
37 | * @param holderId A unique identifier for the holder (e.g., Activity, Fragment).
38 | * @param service The service instance to register.
39 | */
40 | // @JvmStatic
41 | // inline fun registerService(holderId: String, service: T) {
42 | // // Get or create the service map for the holder
43 | // val services = holderServices.getOrPut(holderId) { mutableMapOf() }
44 | // // Register the service using its class name as the key
45 | //// services[T::class.java.name] = service
46 | // services[service.javaClass.name] = service
47 | // Log.d("ServiceManager", "registerService() service " +
48 | // "for holder: $holderId, service.name: ${T::class.java.name}")
49 | // }
50 |
51 | /**
52 | * Unregisters a service associated with a specific holder.
53 | *
54 | * @param holderId A unique identifier for the holder.
55 | * @param service The service instance to unregister.
56 | * @return The unregistered service instance, or null if it was not found.
57 | */
58 | @JvmStatic
59 | fun unregisterService(holderId: String, service: Service): Service? {
60 | // Remove the service from the holder's service map
61 | return holderServices[holderId]?.remove(service.javaClass.name)
62 | }
63 |
64 | /**
65 | * Retrieves a registered service for a specific holder.
66 | *
67 | * @param holderId A unique identifier for the holder.
68 | * @param key The class name of the service (used as the key during registration).
69 | * @return The corresponding service instance, or null if it does not exist.
70 | */
71 | @JvmStatic
72 | fun getService(holderId: String, key: String): Service? {
73 | // Return the service instance associated with the holder ID and key
74 | return holderServices[holderId]?.get(key)
75 | }
76 |
77 | /**
78 | * Retrieves the service instance associated with the specified `holderId`.
79 | *
80 | * This method expects the caller to pass the concrete implementation class of the service,
81 | * not the service interface. For example, if you want to retrieve an instance of `TitleBar`,
82 | * you should call `getService(holderId)` rather than `getService(holderId)`.
83 | *
84 | * @param holderId A unique identifier for the holder.
85 | * @return The service instance associated with the holder, or null if not found.
86 | */
87 | @JvmStatic
88 | inline fun getService(holderId: String): T? {
89 | Log.d("ServiceManager", "getService() for holder: " +
90 | "$holderId, service.name: " + T::class.java.name)
91 | return holderServices[holderId]?.get(T::class.java.name) as? T
92 | }
93 |
94 | /**
95 | * Releases all services registered for a specific holder.
96 | *
97 | * @param holderId A unique identifier for the holder.
98 | */
99 | @JvmStatic
100 | fun releaseHolderServices(holderId: String) {
101 | // Remove and clear all services associated with the holder
102 | holderServices.remove(holderId)?.clear()
103 | }
104 |
105 | /**
106 | * Releases all registered services across all holders.
107 | * Use this with caution, as it affects all managed services.
108 | */
109 | @JvmStatic
110 | fun releaseAllServices() {
111 | // Clear all services for all holders
112 | holderServices.clear()
113 | }
114 | }
--------------------------------------------------------------------------------
/libs/modularization/src/main/java/com/andy/modularization/StateHandle.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 |
6 | interface StateHandle {
7 | /**
8 | * Called when the activity is recovering from a previously saved state (if applicable).
9 | * This method is invoked after the activity is recreated and can be used to restore
10 | * any state that was saved during the previous instance.
11 | */
12 | fun onRestoreInstanceState(savedInstanceState: Bundle?)
13 |
14 | /**
15 | * Called when the activity saves its state before it is stopped or destroyed.
16 | * This method allows the activity to save important data in the `outState` bundle
17 | * that can be restored later if the activity is recreated.
18 | */
19 | fun onSaveInstanceState(outState: Bundle)
20 |
21 | /**
22 | * Optional: Handle new Intent if the activity was already created.
23 | * This method is triggered when a new intent is passed to an activity that is already running.
24 | * Useful for handling changes in data or UI updates based on the new intent.
25 | */
26 | fun onNewIntent(intent: Intent)
27 | }
--------------------------------------------------------------------------------
/libs/modularization/src/test/java/com/andy/modularization/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.andy.modularization
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 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "componentFramework"
23 | include(":app")
24 | include(":libs:modularization")
25 |
--------------------------------------------------------------------------------