mardi 2 juin 2020

How to implement MVC (and loose coupling) in Android development

I'm new with design patterns and Android development. I'm trying to use MVC pattern - which consists in a separation between View, Controller and Models components - for a simple Android app.

My demo app is very simple: you have two buttons; one allows you to increment a number and the other one allows you to reset them if n % 5 == 0.

This is what I did:

activity_main.xml (this should represent the View)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.MainActivity">

    <Button
        android:id="@+id/buttonAzzera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Azzera"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.885"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.499" />

    <Button
        android:id="@+id/buttonIncrementa"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Incrementa"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.148"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.499" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/buttonAzzera"
        app:layout_constraintStart_toEndOf="@+id/buttonIncrementa"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

whose autogenerated .java file is (which should represent the view too)

    public class MainActivity extends AppCompatActivity {

    private Button buttonReset;
    private Button buttonIncrement;
    private TextView numberLabel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initViewComponents();

        Model model = new Model();
        IncrementController incrementController = new IncrementController(this, model);
        ResetController resetController = new ResetController(this, model);

        incrementController.setListener();
        resetController.setListener();
    }

    public void initViewComponents() {
        buttonReset = findViewById(R.id.buttonAzzera);
        buttonIncrement = findViewById(R.id.buttonIncrementa);
        numberLabel = findViewById(R.id.textView);
    }

    public Button getButtonReset() {
        return buttonReset;
    }

    public void setButtonReset(Button buttonReset) {
        this.buttonReset = buttonReset;
    }

    public Button getButtonIncrement() {
        return buttonIncrement;
    }

    public void setButtonIncrement(Button buttonIncrement) {
        this.buttonIncrement = buttonIncrement;
    }

    public TextView getNumberLabel() {
        return numberLabel;
    }

    public void setNumberLabel(TextView numberLabel) {
        this.numberLabel = numberLabel;
    }
}

This is the Model

public class Model {

    private int number;

    public Model() {
        number = 0;
    }

    public int getNumber() {
        return number;
    }

    public boolean isNumberResettable() {
        return number % 5 == 0;
    }

    public void incrementNumber() {
        number++;
    }

    public void resetNumber() {
        if (isNumberResettable()) {
            number = 0;
        }
    }
}

And these are the controllers:

public class IncrementController implements View.OnClickListener {

    private MainActivity view;
    private Model model;

    public IncrementController(MainActivity view, Model model) {
        this.view = view;
        this.model = model;
    }

    public void setListener() {
        view.getButtonIncrement().setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        model.incrementNumber();
        view.getNumberLabel().setText(String.valueOf(model.getNumber()));
        if (model.isNumberResettable())
            view.getButtonReset().setEnabled(true);
        else
            view.getButtonReset().setEnabled(false);
    }
}

public class ResetController implements View.OnClickListener {

    private MainActivity view;
    private Model model;

    public ResetController(MainActivity view, Model model) {
        this.view = view;
        this.model = model;
    }

    public void setListener() {
        view.getButtonReset().setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        model.resetNumber();
        view.getNumberLabel().setText("0");
        view.getButtonReset().setEnabled(false);
    }

}

Here are my questions:

  1. Is my MVC architecture right?

  2. Is it right to have in my MainActivity.java file - which represents the View, I guess - references to the Model and to the Controller(s)?

    • If yes, how can I edit the parameters in a Controller's constructor, for example public IncrementController(MainActivity view, Model model){...} so that the first parameter is not necessarily a MainActivity object but a generic Activity?
  3. Should I have to co-operate with another Design Pattern, such as Observer-Observable, to correctly implement the MVC structure?

Aucun commentaire:

Enregistrer un commentaire