modelo español databinding data android mvvm

español - Implementación correcta de MVVM en Android



mvvm android español (3)

He estado luchando por encontrar la forma correcta de implementar MVVM en Android.

Toda la Idea sigue siendo borrosa para mí, el patrón es tener una capa separada en la que se hace la lógica (ViewModel).

Este fragmento de código solo anima el alfa de un fondo en el que viven varios fragmentos.

public class StartActivity extends AppCompatActivity implements EntryFragment.EntryFragementListener { private static final float MINIMUM_ALPHA = 0.4f; private static final float MAXIMUM_ALPHA = 0.7f; @State float mCurrentAlpha = MINIMUM_ALPHA; @State String mCurrentTag = EntryFragment.TAG; private ActivityStartBinding mBinding; private StartViewModel mStartViewModel = new StartViewModel(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_start); mBinding.setStartViewModel(mStartViewModel); mBinding.bgBlackLayer.setAlpha(mCurrentAlpha); if (getSupportFragmentManager().findFragmentByTag(mCurrentTag) == null) { switch (mCurrentTag) { case EntryFragment.TAG: setEntryFragment(); break; case FreeTrialFragment.TAG: setFreeTrialFragment(); break; } } } private void setEntryFragment() { mCurrentAlpha = MINIMUM_ALPHA; mCurrentTag = EntryFragment.TAG; FragmentManager fm = getSupportFragmentManager(); Fragment fragment = new EntryFragment(); fm.beginTransaction(). add(R.id.fragment_content, fragment, EntryFragment.TAG).commit(); } private void setFreeTrialFragment() { mCurrentTag = FreeTrialFragment.TAG; Fragment fragment = new FreeTrialFragment(); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.anim_enter_right, R.anim.anim_exit_left, R.anim.anim_enter_left, R.anim.anim_exit_right); ft.replace(R.id.fragment_content, fragment, FreeTrialFragment.TAG); ft.addToBackStack(FreeTrialFragment.TAG); ft.commit(); StartViewModel.setAnimation(mBinding.bgBlackLayer,true, MAXIMUM_ALPHA); } private void setForgotPasswordFragmet() { } private void setLoginFragment() { } @Override public void onBackPressed() { super.onBackPressed(); StartViewModel.setAnimation(mBinding.bgBlackLayer,true, MINIMUM_ALPHA); mCurrentAlpha = MINIMUM_ALPHA; } @Override public void onEntryLoginButton() { setLoginFragment(); } @Override public void onEntryFreeTrialButton() { setFreeTrialFragment(); } }

-El ViewModel solo hace la lógica al hacer la animación -Fragmentos tienen un oyente para pasar los eventos a la actividad -La vinculación ayuda a definir las vistas

public class StartViewModel { public ObservableBoolean hasToAnimate = new ObservableBoolean(false); public float alpha; @BindingAdapter(value={"animation", "alpha"}, requireAll=false) public static void setAnimation(View view, boolean hasToAnimate, float alpha) { if (hasToAnimate) { view.animate().alpha(alpha); } } }

La pregunta es: ¿debería residir toda la lógica en el modelo de vista, incluidas las transacciones de fragmentos, la gestión de los cambios de orientación, etc.? ¿Hay una mejor manera de implementar MVVM?


Cuando se trata de patrones de diseño en general. Desea mantener la lógica empresarial lejos de Actividades y fragmentos.

MVVM y MVP son realmente buenas opciones si me preguntas. Pero ya que quiere implementar MVVM. Luego intentaré explicar un poco sobre cómo lo implemento.

La actividad

public class LoginActivity extends BaseActivity { private LoginActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityLoginBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_login); NavigationHelper navigationHelper = new NavigationHelper(this); ToastHelper toastHelper = new ToastHelper(this); ProgressDialogHelper progressDialogHelper = new ProgressDialogHelper(this); viewModel = new LoginActivityViewModel(navigationHelper,toastHelper,progressDialogHelper); binding.setViewModel(viewModel); } @Override protected void onPause() { if (viewModel != null) { viewModel.onPause(); } super.onPause(); } @Override protected void onDestroy() { if (viewModel != null) { viewModel.onDestroy(); } super.onDestroy(); } }

Esta es una actividad bastante simple. Nada especial. Simplemente empiezo por ejemplificar lo que mi viewModel necesita. Porque trato de mantener todo lo específico de Android lejos de él. Todo para facilitar la escritura de pruebas

Luego solo ato el viewmodel a la vista.

La vista

<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.community.toucan.authentication.login.LoginActivityViewModel" /> </data> <RelativeLayout android:id="@+id/activity_login_main_frame" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/background" tools:context="com.community.toucan.authentication.login.LoginActivity"> <ImageView android:id="@+id/activity_login_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="40dp" android:src="@drawable/logo_small" /> <android.support.v7.widget.AppCompatEditText android:id="@+id/activity_login_email_input" android:layout_width="match_parent" android:layout_height="50dp" android:layout_below="@+id/activity_login_logo" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginTop="60dp" android:drawableLeft="@drawable/ic_email_white" android:drawablePadding="10dp" android:hint="@string/email_address" android:inputType="textEmailAddress" android:maxLines="1" android:text="@={viewModel.username}" /> <android.support.v7.widget.AppCompatEditText android:id="@+id/activity_login_password_input" android:layout_width="match_parent" android:layout_height="50dp" android:layout_below="@+id/activity_login_email_input" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:drawableLeft="@drawable/ic_lock_white" android:drawablePadding="10dp" android:hint="@string/password" android:inputType="textPassword" android:maxLines="1" android:text="@={viewModel.password}" /> <Button android:id="@+id/activity_login_main_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/activity_login_password_input" android:layout_centerHorizontal="true" android:layout_marginTop="10dp" android:background="@drawable/rounded_button" android:onClick="@{() -> viewModel.tryToLogin()}" android:paddingBottom="10dp" android:paddingLeft="60dp" android:paddingRight="60dp" android:paddingTop="10dp" android:text="@string/login" android:textColor="@color/color_white" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/activity_login_main_button" android:layout_centerHorizontal="true" android:layout_marginTop="20dp" android:onClick="@{() -> viewModel.navigateToRegister()}" android:text="@string/signup_new_user" android:textSize="16dp" /> <LinearLayout android:id="@+id/activity_login_social_buttons" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:layout_marginBottom="50dp" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/facebook" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/twitter" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/google" /> </LinearLayout> <TextView android:id="@+id/activity_login_social_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/activity_login_social_buttons" android:layout_centerHorizontal="true" android:layout_marginBottom="20dp" android:text="@string/social_account" android:textSize="16dp" /> </RelativeLayout> </layout>

Bastante sencillo desde el lado de la vista. Ato los valores específicos que viewModel necesita para actuar según la lógica que tiene.

https://developer.android.com/topic/libraries/data-binding/index.html Consulte el siguiente enlace para obtener más información sobre cómo funciona la biblioteca de enlace de datos android

The ViewModel

public class LoginActivityViewModel extends BaseViewModel implements FirebaseAuth.AuthStateListener { private final NavigationHelper navigationHelper; private final ProgressDialogHelper progressDialogHelper; private final ToastHelper toastHelper; private final FirebaseAuth firebaseAuth; private String username; private String password; public LoginActivityViewModel(NavigationHelper navigationHelper, ToastHelper toastHelper, ProgressDialogHelper progressDialogHelper) { this.navigationHelper = navigationHelper; this.toastHelper = toastHelper; this.progressDialogHelper = progressDialogHelper; firebaseAuth = FirebaseAuth.getInstance(); firebaseAuth.addAuthStateListener(this); } @Override public void onPause() { super.onPause(); } @Override public void onResume() { super.onResume(); } @Override public void onDestroy() { firebaseAuth.removeAuthStateListener(this); super.onDestroy(); } @Override public void onStop() { progressDialogHelper.onStop(); super.onStop(); } public void navigateToRegister() { navigationHelper.goToRegisterPage(); } public void tryToLogin() { progressDialogHelper.show(); if (validInput()) { firebaseAuth.signInWithEmailAndPassword(username, password) .addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (!task.isSuccessful()) { String message = task.getException().getMessage(); toastHelper.showLongToast(message); } progressDialogHelper.hide(); } }); } } private boolean validInput() { return true; } @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { if (firebaseAuth.getCurrentUser() != null) { navigationHelper.goToMainPage(); } } @Bindable public String getUsername() { return username; } public void setUsername(String username) { this.username = username; notifyPropertyChanged(BR.username); } @Bindable public String getPassword() { return password; } public void setPassword(String password) { this.password = password; notifyPropertyChanged(BR.password); } }

Aquí es donde sucede toda la diversión. Uso las clases de ayuda para mostrar y actuar con el sistema Android. De lo contrario, trato de mantener la lógica lo más limpia posible. Todo está hecho, así que es más fácil para mí crear y probar la lógica.

Tenga en cuenta

Ato el username y la password con la vista. Por lo tanto, cada cambio realizado en EditText se agregará automáticamente al campo. De ese modo. No necesito agregar ningún oyente específico

Espero que este pequeño escaparate pueda ayudarlo a comprender un poco cómo podría implementar MVVM en sus propios proyectos


En cuanto a mí, MVVM, MVP y otros patrones realmente geniales para chicos geniales no tienen un recibo / flujo directo. Por supuesto, tiene muchos tutoriales / recomendaciones / patrones y enfoques sobre cómo implementarlos. Pero de eso se trata en realidad toda la programación, solo necesita encontrar una solución que se adapte a sus necesidades. Dependiendo de la visión de los desarrolladores, puede aplicar muchos principios a su solución para facilitar / acelerar el desarrollo / prueba / soporte.
En su caso, creo que es mejor mover este tipo de lógica a las transiciones de Fragmentos (como lo ha hecho en setFreeTrialFragment() ), es más personalizable y cómodo de usar. Sin embargo, si su enfoque sigue siendo el mismo, el existente es normal. En realidad, @BindingAdapter es más adecuado para atributos xml que para un uso directo.
En cuanto a mí, toda la lógica de la interfaz de usuario debe residir en la actividad, el objetivo principal es separar la lógica empresarial de la interfaz de usuario. Por eso, todas las animaciones, transacciones de fragmentos, etc. se manejan dentro de la actividad, ese es mi enfoque. ViewModel: es responsable de notificar a la vista que algo ha cambiado en el modelo correspondiente y que la vista debe organizarse para esos cambios. En el mundo perfecto, usted debería ser capaz de lograr un término tan popular como el enlace bidireccional, pero no siempre es necesario y no siempre se deben manejar los cambios de la interfaz de usuario dentro del ViewModel. Como de costumbre, demasiado MVVM es malo para su proyecto. Puede causar el código de Spaghetti , "¿De dónde es?", "¿Cómo se puede reciclar?" y otros temas populares. Por lo tanto, debe usarse solo para hacer la vida más atractiva, no para hacer que todo sea ideal, porque como cualquier otro patrón causará mucho dolor de cabeza y alguien que revisará su código dirá "¡EXCESO DE GRANJAS! 11".

Por solicitud, ejemplo de MVP:

Aquí tienes algunos artículos útiles:

  • Ejemplo bastante simple.
  • Aquí tiene una buena descripción con la guía de integración.
  • La primera y la segunda parte de estos artículos pueden ser más útiles.
  • Este es corto y muy descriptivo.

Ejemplo corto (generalizado), debe ajustarlo a su arquitectura:

Representación del paquete:

Implementación:

Modelo:

public class GalleryItem { private String mImagePath; //other variables/getters/setters }

Presentador:

//cool presenter with a lot of stuff public class GalleryPresenter { private GalleryView mGalleryView; public void loadPicturesBySomeCreteria(Criteria criteria){ //perform loading here //notify your activity mGalleryView.setGalleryItems(yourGaleryItems); } //you can use any other suitable name public void bind(GalleryView galleryView) { mGalleryView = galleryView; } public void unbind() { mGalleryView = null; } //Abstraction for basic communication with activity. //We can say that this is our protocol public interface GalleryView { void setGalleryItems(List<GalleryItem> items); } }

Vista:

public class NiceGalleryView extends View { public NiceGalleryView(Context context) { super(context); } public NiceGalleryView(Context context, AttributeSet attrs) { super(context, attrs); } // TODO: 29.12.16 do your stuff here }

Y de cource el código de actividad:

public class GalleryActivity extends AppCompatActivity implements GalleryPresenter.GalleryView { private GalleryPresenter mPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gallery); //init views and so on mPresenter = new GalleryPresenter(); mPresenter.bind(this); } @Override public void setGalleryItems(List<GalleryItem> items) { //use RecyclerView or any other stuff to fill your UI } @Override protected void onDestroy() { super.onDestroy(); mPresenter.unbind(); } }

También tenga en cuenta que incluso tiene muchos enfoques diferentes al usar MVP. Solo quiero enfatizar que prefiero inicializar vistas en actividad y no pasarlas fuera de actividad. Puede gestionar esto a través de la interfaz y eso es realmente cómodo no solo para el desarrollo, sino incluso para pruebas instrumentales.


Un buen ejemplo va aquí, así que compruébalo, vale la pena leerlo, ya que incluye más de 1 forma de incluir la arquitectura MVP. Muestras de MVP Google