preface

In mobile Application Legacy System Reconstruction (8) -dependency Injection section and Mobile Application Legacy System Reconstruction (9) -Routing section, we have completed the basic injection and routing framework construction. Then, mobile application legacy system refactoring (7) – Decoupled Refactoring Demonstration (1)+ video demonstration. In this part, we will refactor the remaining platform package and dynamic package in the App. In this article, the analysis and decoupling ideas and procedures will be listed, and there will be a detailed full demonstration video.

The platform package refactor the code demo: mp.weixin.qq.com/s/YJLBFBD9T…

The dynamic package refactor the code demonstrates: mp.weixin.qq.com/s/ZcDDIrJwU…

Safety refactoring demo

Platform package refactoring

  1. Rely on the analysis of

The platform has a reverse dependency on the upper-layer bundle. After the dependency is removed, the package can be lowered to the platform module.

  1. Security refactoring

Code before refactoring:

public class LoginActivity extends AppCompatActivity { UserController userController = new UserController(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); userController.login("", "", new CallBack() { @Override public void success(String message) { } @Override public void filed(String message) { } }); }}Copy the code

Refactoring techniques: extract agent classes, inline, mobile

  • Since there are login and getUserInfo methods in UserControler, we cannot simply sink the class together. We need to extract a separate class to sink the login method and LoginActivity together

Refactored code:

public class UserController { public static boolean isLogin = false; public final LoginController loginController = new LoginController(); Public Boolean login(String ID, String password, CallBack CallBack) {return loginController.login(id, password, CallBack CallBack) callBack); } public the UserInfo getUserInfo () {/ / get the user information return loginController. GetUserInfo (); }}Copy the code
  • Inline LoginActivity calls to UserController as calls to LoginController

Inline login method:

Inline loginController:

Extract member variables:

Refactored code:

public class LoginActivity extends AppCompatActivity { private LoginController loginController = new LoginController(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); Logincontroller.login ("", "", "") new CallBack() { @Override public void success(String message) { } @Override public void filed(String message) { } }); }}Copy the code
  1. Code mobile

Move the code to a separate platform and add the corresponding Gradle dependency:

  1. Functional verification

Perform smoke tests to verify functionality

./gradlew app:testDeb
ug --tests SmokeTesting

Copy the code

Code demo:Mp.weixin.qq.com/s/YJLBFBD9T…

Concrete code: Github links

The dynamic package refactoring

  1. Rely on the analysis of

Dynamic packages have horizontal dependencies between fileBundle and userBundle. This is mainly because you need to upload and download files dynamically, so you rely on fileBundle. In addition, you need to determine whether to log in, so you also rely on userBundle

  1. Security refactoring

Code before refactoring:

public class DynamicFragment extends Fragment { DynamicController dynamicController = new DynamicController(); FileController fileController = new FileController(new UserStateImpl()); Button btnShare; public static DynamicFragment newInstance() { DynamicFragment fragment = new DynamicFragment(); Bundle args = new Bundle(); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dynamicController.getDynamicList(); FileInfo fileInfo = fileController.upload("/data/data/user.png"); dynamicController.post(new Dynamic(), fileInfo); } } public class DynamicController { FileController fileController=new FileController(new UserStateImpl()); Public Boolean post(Dynamic Dynamic, FileInfo FileInfo) {// send a Dynamic message. UserController.isLogin) { return false; } HttpUtils.post("http://dynamic", LoginController.userId); return true; } public List<Dynamic> getDynamicList() {filecontroller.download (""); return new ArrayList<>(); }}Copy the code

Refactoring techniques: advance proxy classes, extract interfaces, mobile, inline, extract variables, etc

Since FileController has other options for uploading and downloading files as well as fetching files, we can’t simply advance the entire FileController interface, hoping that the responsibilities of the removed interface will be more simple.

  • Extract the FileTransfer class and extract the interface
  • Inline the dependency on FileController as FileTransfer
  • The dependency on UserBundle uses the extracted UserState interface
  • Use injection for dependency management

Because of the more steps, you can directly watch the video demonstration, here is not a screenshot.

Refactored code:

@AndroidEntryPoint public class DynamicFragment extends Fragment { @Inject DynamicController dynamicController; Button btnShare; @Inject TransferFile transferFile; public static DynamicFragment newInstance() { DynamicFragment fragment = new DynamicFragment(); Bundle args = new Bundle(); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dynamicController.getDynamicList(); FileInfo = transferfile. upload("/data/data/user.png"); dynamicController.post(new Dynamic(), fileInfo); } } public class DynamicController { @Inject TransferFile transferFile; @Inject UserState userState; @inject public DynamicController() {} public Boolean post(Dynamic Dynamic, FileInfo FileInfo) {// send a Dynamic message if (! userState.isLogin()) { return false; } HttpUtils.post("http://dynamic", LoginController.userId); return true; } public List<Dynamic> getDynamicList() {transferfile.download (""); transferfile.download (""); return new ArrayList<>(); }}Copy the code
  1. Code mobile

Move the code to a separate Dynamic Bundle and add the corresponding Gradle dependencies:

  1. Functional verification

Perform smoke tests to verify functionality

./gradlew app:testDeb
ug --tests SmokeTesting

Copy the code

Code demo:Mp.weixin.qq.com/s/ZcDDIrJwU…

Concrete code: Github links

ArchUnit

During the refactoring process, we added modules to the API, and we needed to tweak the ArchUnit use case.

@RunWith(ArchUnitRunner.class) @AnalyzeClasses(packages = "com.cloud.disk") public class ArchRuleTest { @ArchTest public  static final ArchRule architecture_layer_should_has_right_dependency =layeredArchitecture() .layer("Library").definedBy(".. cloud.disk.library.." ) .layer("Api").definedBy(".. cloud.disk.api.." ) .layer("PlatForm").definedBy(".. cloud.disk.platform.." ) .layer("FileBundle").definedBy(".. cloud.disk.bundle.file.." ) .layer("DynamicBundle").definedBy(".. cloud.disk.bundle.dynamic.." ) .layer("UserBundle").definedBy(".. cloud.disk.bundle.user.." ) .layer("AllBundle").definedBy(".. cloud.disk.bundle.." ) .layer("App").definedBy(".. cloud.disk.app.." ) .whereLayer("App").mayOnlyBeAccessedByLayers() .whereLayer("FileBundle").mayOnlyBeAccessedByLayers("App") .whereLayer("DynamicBundle").mayOnlyBeAccessedByLayers("App") .whereLayer("UserBundle").mayOnlyBeAccessedByLayers("App")  .whereLayer("PlatForm").mayOnlyBeAccessedByLayers("App","AllBundle") .whereLayer("Api").mayOnlyBeAccessedByLayers("App","AllBundle","PlatForm") .whereLayer("Library").mayOnlyBeAccessedByLayers("App","AllBundle","PlatForm"); }Copy the code

All of the previous architectural design constraints are now fully decoupled, but since the test files and Hilt generate compilation issues, we have to add new configuration files to filter.

Add archunit_ignore_patterns. TXT to the resource folder and add the following filter rules:

.*com.cloud.disk.SmokeTesting.*
.*com.cloud.disk.DaggerSmokeTesting_*.*
Copy the code

To run the architecture guardian test, run the following command:

 ./gradlew app:testDebug --tests ArchRuleTest
Copy the code

We can see that it has been running properly through.

conclusion

After evolving and refactoring, we have now finally passed the architectural guardianship test of our design. Let’s compare the differences with a graph.

With the first phase of decoupled refactoring complete, the CloudDisk team decided to split the team into five teams that manage files, dynamics, user-centric, platform, and common libraries. Instead of using a single Git repository for code management, each team can maintain its own code repository independently.

In the next installment, Refactoring legacy Systems for Mobile applications (11) – Artifact Management, we will share how to decouples modules for binary distribution, where they are no longer compiled with source dependencies and can be managed in a separate Git repository as designed.

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

The outline

about

Welcome to the CAC Agile Coach public account. Wechat search: CAC Agile Coach.

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