Я изо всех сил пытался найти правильный способ реализовать MVVM в Android.
Идея для меня все еще размыта, шаблон должен иметь отдельный слой, в котором выполняется логика (ViewModel).
Этот фрагмент кода анимирует только альфа-канал фона, в котором находится группа фрагментов.
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();
}
}
-The ViewModel выполняет только логику при выполнении анимации -Fragments имеют прослушиватель для передачи событий в действие -Binding помогает определить представления
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);
}
}
}
Вопрос в том, должна ли вся логика находиться в модели представления, включая транзакции фрагментов, управление изменениями ориентации и т. Д.? Есть ли лучший способ реализовать MVVM?
4 ответа
Как по мне - MVVM, MVP и другие действительно крутые шаблоны для действительно крутых парней не имеют прямого получения / потока. Конечно, у вас есть много руководств / рекомендаций / шаблонов и подходов к их реализации. Но на самом деле в этом и заключается вся суть программирования - вам просто нужно найти решение, которое соответствует вашим потребностям. В зависимости от видения вашего разработчика вы можете применить к своему решению множество принципов, чтобы упростить / ускорить разработку / тестирование / поддержку.
В вашем случае я думаю, что лучше перенести эту логику на переходы фрагментов (как вы это делали в setFreeTrialFragment()
), она более настраиваема и удобна в использовании. Но тем не менее, если ваш подход должен остаться прежним - существующий - нормально. На самом деле @BindingAdapter
больше подходит для атрибутов xml, чем для прямого использования.
Как по мне, вся логика UI должна находиться в Activity, основная цель - отделить бизнес-логику от UI. Из-за этого все анимации, транзакции фрагментов и т. Д. Обрабатываются внутри активности - это мой подход. ViewModel - отвечает за уведомление представления о том, что что-то изменилось в соответствующей модели, и представление должно приспособиться к этим изменениям. В идеальном мире вы должны иметь возможность использовать такой популярный термин, как двусторонняя привязка, но это не всегда необходимо, и не всегда изменения пользовательского интерфейса должны обрабатываться внутри ViewModel. Как обычно, слишком много MVVM плохо для вашего проекта. Это может вызвать код спагетти, «откуда это?», «Как переработать представление?» и другие популярные вопросы. Поэтому его следует использовать только для того, чтобы сделать жизнь проще, а не для того, чтобы все было идеально, потому что, как и любой другой шаблон, он вызовет сильную головную боль, и кто-то, кто просмотрит ваш код, скажет: «ПЕРЕРАБОТКА !! 11».
По запросу, пример MVP:
Вот несколько полезных статей:
- Довольно простой пример.
- Здесь у вас есть хорошее описание интеграции руководство.
- Первый и вторая часть этой статьи может быть больше полезный.
- Этот один короткий и очень информативный.
Краткий пример (обобщенный), вы должны подогнать его под свою архитектуру:
Реализация:
Модель:
public class GalleryItem {
private String mImagePath;
//other variables/getters/setters
}
Докладчик:
//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);
}
}
Просмотр:
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
}
И, конечно же, код активности:
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();
}
}
Также имейте в виду, что у вас даже есть много разных подходов при использовании MVP. Я просто хочу подчеркнуть, что я предпочитаю инициализировать представления в действии и не передавать их из активности. Вы можете управлять этим через интерфейс, и это действительно удобно не только для разработки, но даже для инструментальных тестов.
Что касается шаблонов проектирования в целом. Вы хотите, чтобы бизнес-логика находилась подальше от действий и фрагментов.
MVVM и MVP - действительно хороший выбор, если вы спросите меня. Но поскольку вы хотите реализовать MVVM. Затем я постараюсь немного объяснить, как я это реализую.
Действия
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();
}
}
Это довольно простое занятие. Ничего особенного. Я просто начинаю с создания экземпляра того, что нужно моему viewModel. Потому что я стараюсь держать все подальше от Android. Все для облегчения написания тестов
Затем я просто привязываю модель просмотра к представлению.
Вид
<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>
Довольно прямолинейно со стороны обзора. Я привязываю все конкретные значения, необходимые viewModel, чтобы действовать в соответствии с имеющейся у него логикой.
https://developer.android.com/topic/libraries/data- привязка / index.html Перейдите по следующей ссылке, чтобы узнать больше о том, как работает библиотека привязки данных Android.
Модель просмотра
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);
}
}
Здесь происходит все самое интересное. Я использую вспомогательные классы, чтобы показать и работать с системой Android. В противном случае я стараюсь сохранять логику как можно более чистой. Все сделано так, чтобы мне было проще создавать и тестировать логику.
Примечания
Я связал username
и password
с обзором. Таким образом, каждое изменение, внесенное в EditText, будет автоматически добавлено в поле. По тому пути. Мне не нужно добавлять какого-либо конкретного слушателя
Надеюсь, эта небольшая витрина поможет вам немного понять, как можно внедрить MVVM в свои собственные проекты.
Здесь есть хороший пример, поэтому ознакомьтесь с ним, стоит прочитать, поскольку он включает более одного способа включить архитектуру MVP. Примеры MVP Google
Если вас интересует "чистая" аутентификация Firebase, вы можете проверить следующую статью:
Репозиторий ничего не знает о представлениях, а представления ничего не знают о своих источниках данных.
Похожие вопросы
Новые вопросы
android
Android — это мобильная операционная система Google, используемая для программирования или разработки цифровых устройств (смартфонов, планшетов, автомобилей, телевизоров, одежды, очков, IoT). Для тем, связанных с Android, используйте теги, специфичные для Android, такие как android-intent, android-activity, android-adapter и т. д. Для вопросов, отличных от разработки или программирования, но связанных с Android framework, используйте эту ссылку: https://android .stackexchange.com.