È possibile implementare MVP in Android con Attività e Frammenti come Presenter e Visualizzazioni personalizzate?

1

Ultimamente il mio team ha iniziato a considerare l'implementazione del pattern MVP in alcune delle nostre applicazioni.

Abbiamo seguito le diverse guide e tutorial là fuori, fondamentalmente finendo con regolare PresenterInterface e ViewInterface , quest'ultimo essendo attuato da un Activity o Fragment .

Come PresenterInterface , abbiamo creato un'implementazione e l'abbiamo iniettata in Activity con dagger.

Quindi PresenterImplementation manterrà traccia dello stato ed eseguirà tutta la logica dietro il comportamento di Activity .

Potremmo pubblicare con successo alcune applicazioni basate su questo modello e tutto sembra funzionare abbastanza bene.

Ultimamente pensavo, ho iniziato a mettere in discussione questo modo di implementare il pattern MVP come mi sembra che ci sia uno strato logico extra aggiunto tra loro senza essere menzionato:

  1. Non è View la vista attuale? - Voglio dire, si dispone di un Activity e si utilizza setContentView(...) poi con findViewById(...) si ottiene un'istanza della classe View che rappresenta la vista reale analizzato da XML o di programmazione integrata
  2. .
  3. Quindi se View è la vista, non è il Activity il presentatore effettivo? - Voglio dire che il concetto Activity è lì per rappresentare una logica dietro la vista.
  4. Il presentatore dovrebbe mantenere lo stato: beh, esistono diversi modi per mantenere lo stato all'interno e Activity . Ad esempio, SavedInstanceState , SharedPreferences ecc.
  5. Tutti gli% ascoltatori% co_de (ad esempio View ) sono i metodi che la View può invocare il suo presentatore, mentre un composto CustomView può essere creato esporre metodi per impostare i valori dei suoi Sub-Vista con interfaccia.

Con il modello "normale" MVP abbiamo usato fino ad ora, mi sembra che la OnClickListener è lì solo per accedere ai suoi Activity s 'sub-viste e comandi 'porta' del presentatore davanti a loro.

Quindi, nella mia idea, avresti qualcosa di simile a questo:

Struttura

- main
|
|- MainActivity (class)
|- MainPresenter (interface)
|- MainView (interface)
|- MainViewImpl (class)

MainView

public interface MainView {

  void setTitle(String title);
  void setBackgroundColor(int backgroundColor);

}

MainPresenter

public interface MainPresenter {

  void awkwardButtonPressed();

  void bind(MainView mainView);

  void setup();

}

MainViewImpl

public class MainViewImpl extends FrameLayout implements MainView{

  LayoutInflater inflater;
  TextView tvTitle;
  Button btnAwkward;
  MainPresenter mainPresenter;

  public MainViewImpl(Context context) {
    super(context);

    if(!(context instanceof MainPresenter))
      throw new IllegalArgumentException("I need a MainPresenter");
    else mainPresenter = (MainPresenter) context;

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    init();
    mainPresenter.bind(this);
    mainPresenter.setup();
  }

  public MainViewImpl(Context context, AttributeSet attrs) {
    super(context, attrs);

    if(!(context instanceof MainPresenter))
      throw new IllegalArgumentException("I need a MainPresenter");
    else mainPresenter = (MainPresenter) context;

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    init();
    mainPresenter.bind(this);
    mainPresenter.setup();
  }

  public MainViewImpl(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    if(!(context instanceof MainPresenter))
      throw new IllegalArgumentException("I need a MainPresenter");
    else mainPresenter = (MainPresenter) context;

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    init();
    mainPresenter.bind(this);
    mainPresenter.setup();
  }

  @Override
  public void setTitle(String title) {
    tvTitle.setText(title);
  }

  @Override
  public void setBackgroundColor(String backgroundColor) {
    super.setBackgroundColor(Color.parseColor(backgroundColor));
  }

  private void init(){
    inflater.inflate(R.layout.main_view_layout, this, true);
    tvTitle = (TextView) findViewById(R.id.tvTitle);
    btnAwkward = (Button) findViewById(R.id.btnAwkward);
    btnAwkward.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        mainPresenter.awkwardButtonPressed();
      }
    });
  }

}

MainActivity

public class MainActivity extends AppCompatActivity implements MainPresenter {

  private MainView mainView;
  private final Map<String, String> status = new HashMap<>();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
      if (savedInstanceState.containsKey("title")) {
        status.put("title", savedInstanceState.getString("title"));
      }
      if (savedInstanceState.containsKey("backgroundColor")) {
        status.put("backgroundColor", savedInstanceState.getString("backgroundColor"));
      }
    } else {
      status.put("title", "My Cool MVP");
      status.put("backgroundColor", "#00AAFF");
    }

    setContentView(R.layout.activity_main);
  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    outState.putString("title", status.get("title"));
    outState.putString("backgroundColor", status.get("backgroundColor"));
    super.onSaveInstanceState(outState);
  }

  @Override
  public void bind(MainView mainView) {
    this.mainView = mainView;
  }

  @Override
  public void setup() {
    this.mainView.setTitle(status.get("title"));
    this.mainView.setBackgroundColor(status.get("backgroundColor"));
  }

  @Override
  public void awkwardButtonPressed() {
    status.put("backgroundColor", "#00FFAA");
    status.put("title", "That Was Awkward...");
    mainView.setBackgroundColor(status.get("backgroundColor"));
    mainView.setTitle(status.get("title"));
  }
}

Infine le risorse xml per i layout qui potrebbero essere:

main_activity_layout.xml

<com.sample.application.MainViewImpl
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cvMainView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

main_view_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btnAwkward"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/awkward_button"/>

</LinearLayout>

In questo modo View è la vista e MainViewImpl è il presentatore. Nota in entrambe le implementazioni la controparte è sempre accessibile attraverso la sua interfaccia, il Activity mantiene lo stato in una mappa che inserisce Activity quando necessario e recupera nuovamente quando viene ricreato.

Domande

Ha senso? Questo è davvero MVP? In caso contrario, qual è il ruolo effettivo della classe SavedInstanceState in MVP Android? Quali potrebbero essere i difetti dell'implementazione presentata?

Nel caso qualcuno stia vagando su come testare la logica del presentatore (che ora è un Activity ), ecco come può essere fatto:

MainActivityTest

public class MainActivityTest {

  MainPresenter mainPresenter;
  MainView mainView;

  @Before
  public void setUp() {
    mainPresenter = new MainActivity();
    mainView = Mockito.mock(MainView.class);
    mainPresenter.bind(mainView);
  }

  @Test
  public void testSetup() throws Exception {
    mainPresenter.setup();
    Mockito.verify(mainView).setTitle(Mockito.anyString());
    Mockito.verify(mainView).setBackgroundColor(Mockito.anyString());
  }

  @Test
  public void testAwkwardPressing() throws Exception {
    mainPresenter.awkwardButtonPressed();
    Mockito.verify(mainView).setTitle("That Was Awkward...");
    Mockito.verify(mainView).setBackgroundColor("#00FFAA");
  }
}
    
posta Onheiron 04.05.2016 - 10:14
fonte

0 risposte