preface

We completed the binary management transformation through local Maven. In architectural design, the App module is the integration of the overall project and is used for final release. Business bundles are maintained in independent warehouses, and bundles exist in the form of Lib. We need to be able to debug and develop each business Bundle independently.

In this article, we will continue to modify CloudDisk to support Bundle independent debugging.

Debug mode

1. Switch between Lib and Application

We can configure Gradle to enable Module debugging for Lib and Application, which can be used for both running debugging and publishing aar. The downside is that Gradle needs to be configured, and the debugging code and the official code are all in one project.

For example, Gradle is configured as follows:

Def isApp = false if (isApp) {// run the apply plugin: 'com.android.application'} else {// as the library apply plugin: 'com.android.library' } defaultConfig { if (isApp) { applicationId 'com.cloud.filebundle' } }Copy the code

Add the debug directory under SRC, so that the code in the debug directory will be packaged with it, but not with release. This also makes it easy to separate the test code.

We can display the FileFragment directly in DebugActivity, and run as follows:

See Github for a complete code example

2. Add a Debug project for debugging

Next, we demonstrate with DynamicBundle, adding a new module for specialized debugging. The benefit of this approach is that the test code is completely isolated.

Create a new Module named DynamicDebug and add a dependency on the following:

Implementation 'com. Cloud. Disk: API: 1.0.0' implementation project (' : dynamicBundle ')Copy the code

DebugActivity is also added to debug DynamicFragment.

However, a compilation error was found during debugging. The specific log is as follows:

error: [Dagger/MissingBinding] com.cloud.disk.api.file.TransferFile cannot be provided without an @Provides-annotated method. public abstract static class ApplicationC implements DebugApplication_GeneratedInjector, ^ com.cloud.disk.api.file.TransferFile is injected at com.cloud.disk.bundle.dynamic.DynamicFragment.transferFile com.cloud.disk.bundle.dynamic.DynamicFragment is injected at com.cloud.disk.bundle.dynamic.DynamicFragment_GeneratedInjector.injectDynamicFragment(com.cloud.disk.bundle.dynamic.Dyna MicFragment) [com. Cloud. Dynamicdebug. DebugApplication_HiltComponents. ApplicationC - > Com. Cloud. Dynamicdebug. DebugApplication_HiltComponents. ActivityRetainedC - > Com. Cloud. Dynamicdebug. DebugApplication_HiltComponents. ActivityC - > com.cloud.dynamicdebug.DebugApplication_HiltComponents.FragmentC] It is also requested at: com.cloud.disk.bundle.dynamic.DynamicController.transferFileCopy the code

Because DynamicBundle depends on the implementation of the TransferFile and UseState interface, we debug DynamicBundle independently at this time without loading the implementation of the interface. What we recommend at this point is to Mock and not load the concrete implementation.

Add TransferFile and UseState interface Mock as follows:

@Module
@InstallIn(ActivityComponent.class)
public abstract class MockFileModule {
    @Binds
    public abstract TransferFile bindTransferFile(
            MockTransferFileImpl transferFileImpl
    );
}

public class MockTransferFileImpl implements TransferFile {
    @Inject
    public MockTransferFileImpl() {
    }

    @Override
    public FileInfo upload(String s) {
        return new FileInfo();
    }

    @Override
    public FileInfo download(String s) {
        return new FileInfo();
    }
}

Copy the code
@Module @InstallIn(ActivityComponent.class) public abstract class MockUserModule { @Binds public abstract UserState bindUserState( MockUserStateImpl userStateImpl ); } public class MockUserStateImpl implements UserState { @Inject public MockUserStateImpl() { } @Override public String getUserId() { return ""; } @Override public boolean isLogin() { return true; }}Copy the code

After the mock is added, it compiles properly and runs as follows:

See Github for a complete code example

3. Automate tests

This is a highly recommended approach, repeatable and fast feedback. We already had an example set of smoke tests earlier. Next, we test the UserCenterFragment of the userBundle. We also use Robolectric to speed up the feedback.

Gradle adds a dependency configuration as follows:

TestImplementation 'junit: junit: 4.13.2 testImplementation "com. Google. Way, way:" 1.0.1 testImplementation 'com. Tngtech. Archunit: archunit - junit 4:0.15.0' testImplementation 'org. Robolectric: robolectric: 4.5.1' testImplementation 'androidx. Test: core: 1.3.0' testImplementation 'androidx. Test. Ext: junit: 1.1.2' testImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0' testImplementation 'androidx. Test. Espresso: espresso - the intents: 3.3.0' DebugImplementation "androidx fragments: fragments - testing: 1.3.0"Copy the code

An example for writing tests is:

@RunWith(AndroidJUnit4.class) @LargeTest public class UserCenterFragmentTest { @Test public void show_show_user_center_ui_when_click_tab_dynamic() { //given FragmentScenario<UserCenterFragment> scenario = FragmentScenario.launchInContainer(UserCenterFragment.class); scenario.onFragment(activity -> { //then onView(withText("Hello user center fragment")).check(matches(isDisplayed())); }); }}Copy the code

Execute the test command:

 ./gradlew userBundle:testDebug

Copy the code

The running results are as follows:

See Github for a complete code example

conclusion

With the optimization of decoupling, library separation, and compilation and debugging, CloudDisk’s development efficiency has been greatly improved over a period of time.

However, with the evolution of the business, there are many god classes of activities and controllers in the code due to historical reasons. The team found that the independent business bundles were of poor quality and had many bugs. The team decided to optimize and refactor the module class code again, using the new MVP and MVVM architecture to separate the data and view.

In the next article, refactoring legacy Systems in Mobile Applications (13) – Refactoring examples for MVP, we will demonstrate this through videos and articles.

CloudDisk example code

CloudDisk

Series of links

Refactoring legacy Systems for mobile Applications (1) – Start

Refactoring legacy systems for mobile applications (2) – Architecture

Refactoring legacy systems for mobile applications (3) – Examples

Refactoring legacy Systems in Mobile Applications (4) – Analysis

Mobile application legacy System refactoring (5) – Refactoring methods

Refactoring legacy Systems for mobile applications (6) – Test

Mobile application legacy System refactoring (7) – Decoupled refactoring Demonstration (1)+ video demonstration

Refactoring legacy Systems for mobile applications (8) – Dependency Injection

Refactoring legacy systems for mobile applications (9) – Routing

Refactoring legacy Systems in Mobile applications (10) — Decoupled Refactoring (2)

Refactoring legacy systems for mobile applications (11) – Product management

The outline

about

  • Author: Huang Junbin
  • Blog: junbin. Tech
  • GitHub: junbin1011
  • Zhihu: @ JunBin