MVVMHabitComponent

repository·master·Indexed 22 days ago

https://github.com/goldze/mvvmhabitcomponent

An Android development framework that integrates the MVVM design pattern with a component-based architecture. It leverages MVVMHabit for rapid development using Google's Android Architecture Components, OkHttp, RxJava, Retrofit, and Glide, and uses ARouter for module-to-module communication and decoupling. The framework provides a structure for building modular, scalable apps with a Host project and independent business components.

Tokens
3.8K
Snippets
9
Records
12
Agent score
28%

What's inside MVVMHabitComponent

  1. Overview of MVVMHabitComponent

    master

    MVVMHabitComponent provides an Android development solution that combines the MVVM (Model-View-ViewModel) design pattern with Component-based architecture.

    It is specifically designed to help developers quickly build high-quality, maintainable, and modular Android applications by leveraging:

    • MVVMHabit: A rapid development library based on Google's Android Architecture Components (AAC), integrating OkHttp, RxJava, Retrofit, and Glide.
    • ARouter: A routing framework by Alibaba that enables module-to-module communication and decoupling in componentized apps.

    This approach aims to solve the issues of high coupling and slow compilation times found in single-project architectures while improving developer efficiency through data binding.

  2. How MVVM and Componentization work together

    master

    The project combines two distinct architectural layers to optimize Android development:

    1. Design Pattern Layer (MVVM): Uses the MVVM pattern (facilitated by MVVMHabit) to improve development efficiency through Data Binding. This allows for a seamless connection between XML layouts and Java/Kotlin code, similar to how modern web frameworks like Vue or React operate.
    2. Architectural Layer (Componentization): Uses a component-based approach (facilitated by ARouter) to break a large, single-project codebase into smaller, high-cohesion, low-coupling modules. Each component can be compiled independently and loaded by a host App, which supports multi-person team collaboration and reduces build times.
  3. Follow library-base organizational standards

    master

    The library-base module serves as the foundation for all components. To maintain consistency, follow these package conventions:

    • config: Stores global configuration singletons like ModuleLifecycleConfig, ModuleLifecycleReflexs, network ROOT_URL, and file directory paths.
    • contract: Defines communication contracts for RxBus to ensure components use consistent event types.
    • global: Stores global constant keys, such as IntentKeyGlobal (for ARouter parameter keys) and SPKeyGlobal (for SharedPreferences keys).
    • router: Centralizes ARouter path definitions. Instead of hardcoding strings, use static inner classes within a path class (e.g., RouterActivityPath.Main.PAGER_MAIN) to organize routes by component.
  4. Implement Module Isolation and Debug Mode

    master

    Use a global flag in gradle.properties to switch between running a component as a standalone application (for debugging) or as a library (for integration into the Host).

    1. Define the flag in gradle.properties: isBuildModule=false (false = integrated, true = standalone).

    2. Dynamic Plugin Switching: In the component's build.gradle, use the flag to apply either com.android.application or com.android.library.

    3. Manifest Management:

      • For integrated mode, use the standard src/main/AndroidManifest.xml.
      • For standalone mode, use a specialized manifest located at src/main/alone/AndroidManifest.xml that defines the entry point (Activity) and intent filters.
      • Use sourceSets in build.gradle to swap the manifest file based on the isBuildModule flag.
    // gradle.properties
    isBuildModule=false
    
    // component build.gradle
    android {
        sourceSets {
            main {
                if (isBuildModule.toBoolean()) {
                    // Standalone mode: use the debug manifest
                    manifest.srcFile 'src/main/alone/AndroidManifest.xml'
                } else {
                    // Integrated mode: use standard manifest and exclude debug files
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                    resources {
                        exclude 'src/main/alone/*'
                    }
                }
            }
        }
    }
  5. Configure MVVMHabit and ARouter dependencies

    master

    The project requires MVVMHabit and ARouter. You can use remote dependencies by configuring your repositories and dependency blocks.

    MVVMHabit Setup: Add jitpack.io to your allprojects repositories and implement the dependency.

    ARouter Setup: Configure the AROUTER_MODULE_NAME argument in annotationProcessorOptions so the compiler knows which module is being processed, then add the API and compiler dependencies.

    // MVVMHabit Repository
    allprojects {
        repositories {
            google()
            jcenter()
            maven { url 'https://jitpack.io' }
        }
    }
    
    // MVVMHabit Dependency
    dependencies {
        implementation 'com.github.goldze:MVVMHabit:?'
    }
    
    // ARouter Configuration
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
    
    dependencies {
        api 'com.alibaba:arouter-api:?'
        annotationProcessor 'com.alibaba:arouter-compiler:?'
    }
  6. Use RxBus for global event communication

    master

    For cross-component event communication (where data needs to be sent back or broadcasted), use RxBus. Note that RxBus is not effective across different processes.

    Posting an Event

    To send data (e.g., from Component B back to Component A):

    _Login _login = new _Login();
    RxBus.getDefault().post(_login);

    Subscribing to an Event

    To receive an event in Component A, subscribe to the specific class type. Ensure you unregister the subscription to prevent memory leaks:

    subscribe = RxBus.getDefault().toObservable(_Login.class)
        .subscribe(new Consumer<_Login>() {
            @Override
            public void accept(_Login l) throws Exception {
                // Handle the event (e.g., refresh data)
                initData();
                // Unregister
                RxSubscriptions.remove(subscribe);
            }
        });
    RxSubscriptions.add(subscribe);
    // Posting
    RxBus.getDefault().post(new MyEvent("data"));
    
    // Subscribing
    Disposable subscribe = RxBus.getDefault().toObservable(MyEvent.class)
        .subscribe(event -> {
            // Handle event
        });
    RxSubscriptions.add(subscribe);
  7. Set up the Host and Component Modules

    master

    To build a componentized project, start by creating a Host (the main shell project) and several Components (modules).

    1. Host Project: Create a standard Android project via File -> New -> New Project.... The Host's responsibility is to aggregate all components into a single APK. It primarily contains the AndroidManifest.xml for application configuration and build.gradle for managing dependencies.
    2. Components: Create modules via File -> New -> New Module -> Android Library.... These modules are special: they act as com.android.library when being merged into the Host, but can be configured to act as com.android.application for independent testing.
    3. Base Libraries: Create two essential foundation libraries:
      • library-base: Stores common methods, constants, and communication contracts. It acts as the core dependency for all components.
      • library-res: A dedicated resource library for images, styles, animations, and colors to reduce the load on library-base.
    // Component build.gradle dynamic plugin switching
    if (isBuildModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
  8. Configure Component Dependency Hierarchy

    master

    Establish a clear dependency chain to ensure modularity:

    1. Host $\rightarrow$ Business Components: The Host project should implement all business modules (e.g., module-home, module-user).
    2. Business Components $\rightarrow$ library-base: All business modules should use api project(':library-base') to access common logic.
    3. library-base $\rightarrow$ Common Libraries & library-res: The base library should depend on library-res, the MVVMHabit framework, ARouter API, and other common utilities (like image pickers or push services).
    // Host dependencies
    dependencies {
        implementation project(':module-main')
        implementation project(':module-sign')
        implementation project(':module-home')
        // ... other modules
    }
    
    // Business component dependencies
    dependencies {
        api project(':library-base')
    }
    
    // library-base dependencies
    dependencies {
        api project(':library-res')
        api rootProject.ext.dependencies.MVVMHabit
        api rootProject.ext.dependencies["arouter-api"]
    }
  9. Standardize Resources and Build Configurations

    master

    To maintain a clean componentized architecture, follow these two practices:

    1. Resource Prefixing: Use resourcePrefix in each component's build.gradle to enforce a naming convention (e.g., module_home_) and prevent resource name collisions during merging.
    2. Configuration Extraction: Create a shared module.build.gradle file containing common configurations (ARouter setup, DataBinding, sourceSets, etc.). Components can then include this file using apply from: "../module.build.gradle" to reduce boilerplate.
    // component build.gradle
    apply from: "../module.build.gradle"
    
    android {
        // Enforce prefixing for all resources in this module
        resourcePrefix "module_name_"
    }
    
    dependencies {
        // Module specific dependencies
    }
  10. Initialize components using IModuleInit and Reflection

    master

    In a componentized architecture, the host App's Application class is the only one that can run. To initialize individual components, implement the IModuleInit interface in your component and use reflection to trigger it from the host App.

    Implementation Steps:

    1. Implement IModuleInit in your component: Create an initialization class that implements IModuleInit. This allows you to control the order of initialization.

      • onInitAhead(Application application): For high-priority libraries (e.g., ARouter) that need to be initialized as early as possible.
      • onInitLow(Application application): For lower-priority components.
    2. Register the component: Register the full class path of your initialization class in ModuleLifecycleReflexs to allow dynamic invocation via reflection.

    3. Trigger initialization in the Host Application: Call the ModuleLifecycleConfig singleton methods within the host's onCreate() method.

    Note: Ensure that the Module classes used for initialization are not obfuscated during the build process, otherwise reflection will fail.

    // 1. Implement the interface in your component
    public class MyComponentInit implements IModuleInit {
        @Override
        public boolean onInitAhead(Application application) {
            // High priority initialization (e.g., ARouter)
            ARouter.init(application);
            return false;
        }
    
        @Override
        public boolean onInitLow(Application application) {
            // Lower priority initialization
            return false;
        }
    }
    
    // 2. In the Host Application's onCreate
    @Override
    public void onCreate() {
        super.onCreate();
        // Initialize high-priority components
        ModuleLifecycleConfig.getInstance().initModuleAhead(this);
        
        // ... other logic ...
    
        // Initialize low-priority components
        ModuleLifecycleConfig.getInstance().initModuleLow(this);
    }
  11. Implement component-to-component communication via ARouter

    master

    Since components are decoupled, use ARouter as the bridge for navigation and parameter passing. ARouter should be a dependency in library-base so all components can access it.

    To navigate from Component A to a page in Component B:

    ARouter.getInstance()
        .build(router_url)
        .withString(key, value)
        .navigation();

    Receiving Parameters

    In the target Activity/Fragment in Component B, use the @Autowired annotation to inject the passed values:

    @Autowired(name = key)
    String value;
    // Navigating from Component A
    ARouter.getInstance()
        .build("/componentB/targetActivity")
        .withString("user_name", "goldze")
        .navigation();
    
    // Receiving in Component B
    @Autowired(name = "user_name")
    String userName;