M
M
ministry_of_silly_walks2019-10-24 23:31:24
Android
ministry_of_silly_walks, 2019-10-24 23:31:24

Android development: what should be the scalable code?

Good day, colleagues.
I am a beginner android developer and have no experience in commercial projects yet.
My own projects contain a small number of activities/screens.
Tell me please, where can I read about the "rules of etiquette" for applications with 100+ screens?
Those. What is the difference between highly scalable code and badly scalable code in Android?

Answer the question

In order to leave comments, you need to log in

3 answer(s)
T
terminator-light, 2019-10-26
@ministry_of_silly_walks

In order to write a scalable one, you need to know not only GOF patterns,
but also architectural ones: MVC, MVP, MVVM, MVI. It is important to study the advantages and disadvantages of each.
The principles of SOLID are also at the forefront, below I will give small examples:
1. Single Responsibility principle - the principle of single responsibility. A method/class should only perform one task. For example, a method designed to download data from the network should not deal with error handling.
I'll write in pseudocode:

spoiler
public void loadData(String url){
  repository.fetchProducts().get(products->{
    view.showProducts(products);
  }, throwable ->{
    if(throwable instanceof IOexception){
      view.showNoNetwork();
    }else(throwable instanceof HTTPException){
      HTTPException exception = (HTTPException)throwable;
     	switch(exception.getCode()){
     		case 400:
     			view.showError(exception.getMessage());
     			break;
     		case 401:
     			view.showUnauthorized();
     			break;
     			...
     	}
    }
    ...
  });
}

Instead, the second part needs to be separated into another method/class.
spoiler
public void loadData(String url){
  repository.fetchProducts().get(products-> view.showProducts(products), 
    throwable -> ErrorUtil.handleError(throwable, view));
}

public class ErrorUtil{
  public static void handleError(Throwable throwable, View view){
    if(throwable instanceof IOexception){
      view.showNoNetwork();
    }else(throwable instanceof HTTPException){
      HTTPException exception = (HTTPException)throwable;
     	switch(exception.getCode()){
     		case 400:
     			view.showError(exception.getMessage());
     			break;
     		case 401:
     			view.showUnauthorized();
     			break;
     			...
     	}
    }
    ...
  }
}

2. Open/Closed principle - the principle of openness/closeness. Code should be open to add functionality, but closed to change.
For example, there is such a code for working with a toolbar. If there are a lot of screens with different toolbars,
then you will constantly have to add a new case branch, which means changing the ToolbarManager class,
while there is a possibility of an error in places related to other case branches
spoiler
public class ToolbarManager{
  public void showToolbar(int type){
    switch(type){
      case MAIN:
        ....
        //огромный кусок кода для показа тулбара для главного экрана
        ....
        break;
      case PROFILE:
        ...
        //огромный кусок кода для показа тулбара для экрана профиля
        ...
      ...
    }
  }
}

Solution: use one of the principles of OOP - polymorphism. Now, if you need to add
a new screen, you just need to implement the interface, and this will not affect the code of other screens.
spoiler
public interface ToolbarManager{
  void showToolbar();
}

public class MainToolbarManager implements ToolbarManager{
  public void showToolbar(){
    ....
    //огромный кусок кода для показа тулбара для главного экрана
    ....
  }
}

public class ProfileToolbarManager implements ToolbarManager{
  public void showToolbar(){
    ....
    //огромный кусок кода для показа тулбара для экрана профиля
    ....
  }
}

3. Liskov Substitution principle - Barbara Liskov's substitution principle states: If class B is a subtype of A, then we should be
able to replace A with B without breaking the behavior of the program.
Can't think of an Android related example for this principle, but I liked this example
taken from this site https://www.baeldung.com/solid-principles
spoiler
public interface Car {
    void turnOnEngine(); //запустить двигатель
    void accelerate(); //подать газ
}

public class MotorCar implements Car {
 
    private Engine engine;
 
    public void turnOnEngine() {
        //вруби мотор!
        engine.on();
    }
 
    public void accelerate() {
        //поезжай вперед!
        engine.powerOn(1000);
    }
}

Our class satisfies the interface, we have a car that has its own motor,
and we can accelerate. But we live in 2019, and Elon Musk is a diligent person. We live in the era of electric cars:
spoiler
public class ElectricCar implements Car {
 
    public void turnOnEngine() {
        throw new AssertionError("А у меня вообще нет двигателя");
    }
 
    public void accelerate() {
        //ускорение сумасшедшее!
    }
}

Throwing a car without an engine into a common pile, we change the behavior of our program.
This is a gross violation of Barbara Liskov's substitution principle. Solving this problem will not be easy.
But one solution would be to split our model into small interfaces that take into account the state without the engine
spoiler
public interface Engineful {

    void turnOnEngine();
}
public interface Acceleratable {
    void accelerate();
}
public class MotorCar implements Engineful, Acceleratable {
 
    private Engine engine;
 
    public void turnOnEngine() {
        //вруби мотор!
        engine.on();
    }
 
    public void accelerate() {
        //поезжай вперед!
        engine.powerOn(1000);
    }
}
public class ElectricCar implements Acceleratable {
    public void accelerate() {
        //ускорение сумасшедшее!
    }
}

4. Interface segregation principle - the principle of separation of the interface.
Create faceted small interfaces. Client code should not depend on interface features that it
will not use. Let's take an example from the Android SDK, removed some details for simplicity:
spoiler
public interface TextWatcher{
    public void beforeTextChanged(CharSequence s, int start, int count, int after);
    public void onTextChanged(CharSequence s, int start, int before, int count);
    public void afterTextChanged(Editable s);
}

And client code:
spoiler
editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
    	//метод, который нам нужен
    	//какие-то полезные действия
    }
});

As you can see, we do not need other methods, and we do not use them. This is a clear violation of the ISP,
tk. the interface imposes the use of other methods
5. Dependency inversion principle - the principle of dependency inversion.
High-level modules should not depend on low-level ones.
Abstractions should not depend on details. But details depend on abstractions.
Example: we have a Repository class responsible for getting data from different sources.
The problem is that objects are hardcoded in the constructor. And we do not have the opportunity to
change the implementation of AppDataBase to FakeDataBase for tests.
spoiler
public class Repository{
  private final NetworkManager networkManager;
  private final AppDataBase appDataBase;
  public Repository(){
    this.networkManager = new NetworkManager();
    this.AppDataBase = new AppDataBase();
  }
}

Therefore, we should somehow untie the hard link by highlighting the interfaces.
spoiler
public interface RemoteDataSource{}
public interface LocalDataSource{}
public class NetworkManager implements RemoteDataSource{}
public class AppDataBase implements LocalDataSource{}

public class Repository{
  private final RemoteDataSource remoteDataSource;
  private final LocalDataSource localDataSource;
  public Repository(RemoteDataSource remoteDataSource, LocalDataSource localDataSource){
    this.remoteDataSource = remoteDataSource;
    this.localDataSource = localDataSource;
  }
}

And now we can test the implementation of the sources:
public class FakeNetworkManager implements RemoteDataSource{}
public class FakeAppDataBase implements LocalDataSource{}

and call:
Martin Fowler has a good book Refactoring: Improving Existing Code and R. Martin Clean Code

O
orbit070, 2019-10-25
@orbit070

Poorly scalable is when you have girls in the same class dancing and cows grazing. Google 'android application architecture', mvvm, mvp. In a nutshell, the division of the application into 'layers' allows you to scale and maintain the application normally, you will find examples of such division by googling the above

B
betterxyz, 2019-10-25
@betterxyz

In any professional book on Android programming.
For example https://www.labirint.ru/books/596118/
Information

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question