refactor(mvvm): 重构闹钟与应用列表ViewModel为AndroidViewModel
将ViewModel改为继承BaseAndroidViewModel,通过构造器注入Application,使用addDisposable管理RxJava订阅,LiveData改为私有并提供getter,移除RxLifecycle依赖。
This commit is contained in:
1 parent
7e8877a0db
commit
bbf6cc6679
57 files changed
+2236
-780
No files matched your search
@@ -1,44 +1,135 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.ttstd.dialer.base.rx.BaseRxFragment;
|
||||
|
||||
/**
|
||||
* Fragment 基类,提供标准的懒加载框架
|
||||
* <p>
|
||||
* 兼容两种宿主场景:
|
||||
* 1. ViewPager(v1) + FragmentPagerAdapter:通过 {@link #setUserVisibleHint} 判断可见性(老接口,deprecated)
|
||||
* 2. ViewPager2 / FragmentTransaction.setMaxLifecycle:通过 onResume/onPause 判断可见性
|
||||
* <p>
|
||||
* 懒加载核心思路:
|
||||
* 1. 视图创建完成({@code isViewInitiated}) + 当前页可见({@code isVisibleToUser}) 两个条件同时满足时,
|
||||
* 才触发首次加载({@link #onLazyLoad()}),之后切回该页不再重复触发。
|
||||
* 2. {@link #fetchData()} 每次可见都会回调,用于需要刷新的场景。
|
||||
* 3. View 销毁重建后自动重置懒加载状态,保证重建后的页面能重新渲染。
|
||||
*/
|
||||
public abstract class BaseFragment extends BaseRxFragment {
|
||||
|
||||
private static final String TAG = "BaseFragment";
|
||||
|
||||
/** 视图是否已创建(onViewCreated 之后为 true) */
|
||||
protected boolean isViewInitiated;
|
||||
/** 当前 Fragment 是否对用户可见 */
|
||||
protected boolean isVisibleToUser;
|
||||
/** 首次数据是否已初始化 */
|
||||
protected boolean isDataInitiated;
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
public void onViewCreated(@androidx.annotation.NonNull android.view.View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
isViewInitiated = true;
|
||||
// 视图已创建,检查是否满足懒加载条件
|
||||
prepareFetchData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// ViewPager2 / setMaxLifecycle 场景下没有 setUserVisibleHint 回调,用 onResume 判断可见性;
|
||||
// 兼容 ViewPager(v1):off-screen 页 getUserVisibleHint()==false,不会被误判为可见
|
||||
if (!isViewInitiated || isHidden() || !getUserVisibleHint()) {
|
||||
return;
|
||||
}
|
||||
setVisibleToUser(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
// ViewPager2 / setMaxLifecycle 场景下用 onPause 判断不可见
|
||||
if (isViewInitiated && !isHidden() && getUserVisibleHint()) {
|
||||
setVisibleToUser(false);
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
// View 销毁重建后,重置懒加载状态,保证重建后的页面能重新渲染控件
|
||||
// 否则 Fragment 实例保留的 isDataInitiated=true 会导致重建后不再触发 onLazyLoad,控件消失
|
||||
isDataInitiated = false;
|
||||
isViewInitiated = false;
|
||||
isVisibleToUser = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewPager(v1) + FragmentPagerAdapter 场景下,通过此回调精确判断当前页是否可见
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
this.isVisibleToUser = isVisibleToUser;
|
||||
if (!isViewInitiated) {
|
||||
return;
|
||||
}
|
||||
setVisibleToUser(isVisibleToUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一更新可见性并触发懒加载判断
|
||||
*/
|
||||
private void setVisibleToUser(boolean visible) {
|
||||
if (this.isVisibleToUser == visible) {
|
||||
return;
|
||||
}
|
||||
this.isVisibleToUser = visible;
|
||||
prepareFetchData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发真实数据加载,子类重写实现具体加载逻辑
|
||||
*/
|
||||
public abstract void fetchData();
|
||||
|
||||
/**
|
||||
* 懒加载入口:视图已创建 且 当前页可见 时触发一次
|
||||
*/
|
||||
public boolean prepareFetchData() {
|
||||
return prepareFetchData(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 懒加载入口
|
||||
*
|
||||
* @param forceUpdate 为 true 时,即使已加载过也强制重新加载
|
||||
*/
|
||||
public boolean prepareFetchData(boolean forceUpdate) {
|
||||
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
|
||||
if (!isDataInitiated) {
|
||||
Log.d(TAG, "lazyLoad trigger: " + getClass().getSimpleName());
|
||||
onLazyLoad();
|
||||
}
|
||||
// 只在非强制刷新时标记为已初始化,保证首屏只渲染一次
|
||||
if (!forceUpdate) {
|
||||
isDataInitiated = true;
|
||||
}
|
||||
fetchData();
|
||||
//注释掉保证每次都更新数据
|
||||
// isDataInitiated = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 首次可见时回调,用于做一次性的重操作(如创建控件、加载图片等)
|
||||
* 默认空实现,子类按需重写
|
||||
*/
|
||||
protected void onLazyLoad() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.ttstd.dialer.base.mvvm;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
/**
|
||||
* MVVM ViewModel 基类(带 Context 版本)
|
||||
*
|
||||
* 继承 {@link AndroidViewModel},可通过 {@link #getAppContext()} 获取 Application 级别的 Context。
|
||||
* Application Context 生命周期与进程一致,不会造成内存泄漏。
|
||||
*
|
||||
* 职责:
|
||||
* - 持有 UI 状态(LiveData)
|
||||
* - 处理业务逻辑
|
||||
* - 暴露数据给 View 层
|
||||
* - 通过 {@link #addDisposable(Disposable)} 统一管理 RxJava 异步任务
|
||||
* - 可通过 {@link #getAppContext()} 获取 Application Context(安全)
|
||||
*
|
||||
* 禁止:
|
||||
* - 持有任何 View 引用(Activity / Fragment / View)
|
||||
* - 使用 Activity Context 或持有 View 的 Context(只能使用 Application)
|
||||
* - 直接进行网络请求或数据库操作(必须通过 Repository)
|
||||
* - 在 ViewModel 中弹 Toast / Dialog / Snackbar
|
||||
* - 手动创建 Thread / Executor / AsyncTask 执行异步任务(必须使用 RxJava + {@link #addDisposable(Disposable)})
|
||||
* - 忘记将 Disposable add 到 {@link #mDisposables} 中
|
||||
*/
|
||||
public abstract class BaseAndroidViewModel extends AndroidViewModel implements IMvvmUiState {
|
||||
|
||||
// ============================================================
|
||||
// 通用 UI 状态
|
||||
// ============================================================
|
||||
|
||||
/** 全局加载状态 */
|
||||
private final MutableLiveData<Boolean> mLoadingLiveData = new MutableLiveData<>(false);
|
||||
|
||||
/** 全局错误提示 */
|
||||
private final MutableLiveData<String> mErrorLiveData = new MutableLiveData<>();
|
||||
|
||||
/** 全局成功提示 */
|
||||
private final MutableLiveData<String> mSuccessLiveData = new MutableLiveData<>();
|
||||
|
||||
// ============================================================
|
||||
// RxJava 异步管理(核心)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* CompositeDisposable 用于统一管理所有 RxJava 订阅。
|
||||
* 在 onCleared() 中自动 dispose,防止内存泄漏。
|
||||
*/
|
||||
protected final CompositeDisposable mDisposables = new CompositeDisposable();
|
||||
|
||||
// ============================================================
|
||||
// 构造方法
|
||||
// ============================================================
|
||||
|
||||
public BaseAndroidViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 对外暴露(只读)
|
||||
// ============================================================
|
||||
|
||||
public LiveData<Boolean> getLoadingLiveData() {
|
||||
return mLoadingLiveData;
|
||||
}
|
||||
|
||||
public LiveData<String> getErrorLiveData() {
|
||||
return mErrorLiveData;
|
||||
}
|
||||
|
||||
public LiveData<String> getSuccessLiveData() {
|
||||
return mSuccessLiveData;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 受保护的方法(子类调用)
|
||||
// ============================================================
|
||||
|
||||
protected void setLoading(boolean loading) {
|
||||
mLoadingLiveData.postValue(loading);
|
||||
}
|
||||
|
||||
protected void setError(String message) {
|
||||
mErrorLiveData.postValue(message);
|
||||
}
|
||||
|
||||
protected void setSuccess(String message) {
|
||||
mSuccessLiveData.postValue(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:将 Disposable 添加到 {@link #mDisposables} 中管理。
|
||||
* 子类在发起 RxJava 请求时调用此方法。
|
||||
*/
|
||||
protected void addDisposable(Disposable disposable) {
|
||||
mDisposables.add(disposable);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Context 安全访问
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 获取 Application Context(安全,不会泄漏)。
|
||||
* 子类可通过此方法访问:
|
||||
* - getApplication().getSharedPreferences(...)
|
||||
* - getApplication().getResources().getString(...)
|
||||
* - getApplication().getSystemService(...)
|
||||
*/
|
||||
@NonNull
|
||||
protected Application getAppContext() {
|
||||
return getApplication();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 生命周期
|
||||
// ============================================================
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
// ViewModel 销毁时,自动取消所有正在执行的 RxJava 任务
|
||||
if (!mDisposables.isDisposed()) {
|
||||
mDisposables.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.ttstd.dialer.base.mvvm;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
@@ -14,6 +16,33 @@ import com.ttstd.dialer.utils.Logger;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
||||
/**
|
||||
* MVVM Activity 基类
|
||||
*
|
||||
* 职责:
|
||||
* - 绑定 DataBinding
|
||||
* - 初始化 ViewModel(默认通过反射自动创建)
|
||||
* - 订阅 ViewModel 中的 LiveData({@link #observeViewModel()})
|
||||
* - 订阅全局通用状态({@link #observeCommonState()})
|
||||
* - 页面创建完成后加载数据({@link #onLoadData()})
|
||||
* - 将用户操作转发给 ViewModel
|
||||
*
|
||||
* 生命周期顺序(onCreate):
|
||||
* 1. 创建 DataBinding
|
||||
* 2. 创建 ViewModel({@link #createViewModel()})
|
||||
* 3. 订阅通用状态({@link #observeCommonState()})
|
||||
* 4. 订阅业务状态({@link #observeViewModel()})
|
||||
* 5. 初始化界面({@link #initDataBinding()} / {@link #initView()})
|
||||
* 6. 加载数据({@link #onLoadData()})
|
||||
*
|
||||
* 禁止:
|
||||
* - 在 Activity 中直接进行网络请求或数据库操作(必须通过 ViewModel → Repository)
|
||||
* - 持有可变 LiveData 的引用(应通过 LiveData 回调观察)
|
||||
* - 在 Activity 中写业务逻辑
|
||||
*
|
||||
* @param <VM> ViewModel 类型,必须继承 {@link BaseViewModel} 或 {@link BaseAndroidViewModel}
|
||||
* @param <VDB> ViewDataBinding 类型
|
||||
*/
|
||||
public abstract class BaseMvvmActivity<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseTransparentActivity {
|
||||
|
||||
private static final String TAG = BaseMvvmActivity.class.getSimpleName();
|
||||
@@ -22,39 +51,131 @@ public abstract class BaseMvvmActivity<VM extends ViewModel, VDB extends ViewDat
|
||||
protected VDB mViewDataBinding;
|
||||
protected Class<VM> vmClass;
|
||||
|
||||
// ============================================================
|
||||
// 生命周期
|
||||
// ============================================================
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
//ViewDataBinding
|
||||
|
||||
// 1. 创建 DataBinding
|
||||
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
|
||||
mViewDataBinding.setLifecycleOwner(this);
|
||||
//ViewModel
|
||||
vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
boolean isAbstract = Modifier.isAbstract(vmClass.getModifiers());
|
||||
Logger.e(TAG, "isLocalClass:" + vmClass.getSimpleName().equals(ViewModel.class.getSimpleName()) + " isAbstract:" + isAbstract);
|
||||
if (!isAbstract) {//不是一个抽象类
|
||||
mViewModel = new ViewModelProvider(this).get(vmClass);
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
BaseViewModel<?, ?> baseVm = (BaseViewModel<?, ?>) mViewModel;
|
||||
baseVm.setContext(this);
|
||||
baseVm.setVDBinding(mViewDataBinding);
|
||||
baseVm.setLifecycle(getLifecycleSubject());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 创建 ViewModel
|
||||
mViewModel = createViewModel();
|
||||
|
||||
// 3. 订阅通用状态
|
||||
observeCommonState();
|
||||
|
||||
// 4. 订阅业务状态
|
||||
observeViewModel();
|
||||
|
||||
// 5. 初始化界面
|
||||
initDataBinding();
|
||||
initView();
|
||||
initData();
|
||||
|
||||
// 6. 加载数据
|
||||
onLoadData();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 抽象方法(子类必须实现)
|
||||
// ============================================================
|
||||
|
||||
/** 创建 ViewModel 实例(默认通过反射自动创建,子类可按需重写) */
|
||||
protected VM createViewModel() {
|
||||
vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
boolean isAbstract = Modifier.isAbstract(vmClass.getModifiers());
|
||||
if (isAbstract) {
|
||||
Logger.e(TAG, "ViewModel is abstract: " + vmClass.getSimpleName());
|
||||
return null;
|
||||
}
|
||||
return new ViewModelProvider(this).get(vmClass);
|
||||
}
|
||||
|
||||
/** 初始化 DataBinding(如设置 setClick、绑定表达式等) */
|
||||
protected abstract void initDataBinding();
|
||||
|
||||
/**
|
||||
* 初始化视图
|
||||
*/
|
||||
/** 初始化视图(如设置 Adapter、Listener 等) */
|
||||
protected abstract void initView();
|
||||
|
||||
/** 订阅 ViewModel 中的业务 LiveData */
|
||||
protected abstract void observeViewModel();
|
||||
|
||||
// ============================================================
|
||||
// 可选重写
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
* 页面创建完成后加载数据
|
||||
* 子类可重写此方法触发首次数据加载
|
||||
*/
|
||||
protected abstract void initData();
|
||||
protected void onLoadData() {
|
||||
// 默认空实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅全局通用状态(loading / error / success)
|
||||
* 子类可在 {@link #observeViewModel()} 中调用 super.observeCommonState(),或直接重写
|
||||
*/
|
||||
protected void observeCommonState() {
|
||||
if (!(mViewModel instanceof IMvvmUiState)) return;
|
||||
IMvvmUiState vm = (IMvvmUiState) mViewModel;
|
||||
|
||||
vm.getLoadingLiveData().observe(this, loading -> {
|
||||
if (loading != null && loading) {
|
||||
showLoading();
|
||||
} else {
|
||||
hideLoading();
|
||||
}
|
||||
});
|
||||
|
||||
vm.getErrorLiveData().observe(this, error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
showError(error);
|
||||
}
|
||||
});
|
||||
|
||||
vm.getSuccessLiveData().observe(this, msg -> {
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
showSuccess(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UI 辅助方法(子类可重写)
|
||||
// ============================================================
|
||||
|
||||
protected void showLoading() {
|
||||
// 默认实现:可替换为自定义 LoadingDialog
|
||||
// LoadingDialog.show(this);
|
||||
}
|
||||
|
||||
protected void hideLoading() {
|
||||
// LoadingDialog.dismiss();
|
||||
}
|
||||
|
||||
protected void showError(String message) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
protected void showSuccess(String message) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 禁止使用的模式(编译期约束)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* @deprecated 禁止在 MVVM Activity 中直接创建 Handler 做延时操作
|
||||
* → 应使用 ViewModel + LiveData 驱动
|
||||
*/
|
||||
@Deprecated
|
||||
protected Handler getHandler() {
|
||||
throw new UnsupportedOperationException("禁止在 MVVM Activity 中使用 Handler");
|
||||
}
|
||||
}
|
||||
@@ -1,75 +1,106 @@
|
||||
package com.ttstd.dialer.base.mvvm;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.databinding.ViewDataBinding;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
public abstract class BaseViewModel<VDB extends ViewDataBinding, T> extends ViewModel {
|
||||
/**
|
||||
* MVVM ViewModel 基类(无 Context 版本)
|
||||
*
|
||||
* 职责:
|
||||
* - 持有 UI 状态(LiveData)
|
||||
* - 处理业务逻辑
|
||||
* - 暴露数据给 View 层
|
||||
* - 通过 {@link #addDisposable(Disposable)} 统一管理 RxJava 异步任务
|
||||
*
|
||||
* 禁止:
|
||||
* - 持有任何 View 引用(Activity / Fragment / View / Context)
|
||||
* - 直接进行网络请求或数据库操作(必须通过 Repository)
|
||||
* - 在 ViewModel 中弹 Toast / Dialog / Snackbar
|
||||
* - 手动创建 Thread / Executor / AsyncTask 执行异步任务(必须使用 RxJava + {@link #addDisposable(Disposable)})
|
||||
* - 忘记将 Disposable add 到 {@link #mDisposables} 中
|
||||
*
|
||||
* 若需要 Context / Application(如 SharedPreferences、AlarmManager 等),请继承
|
||||
* {@link BaseAndroidViewModel},通过 {@code getAppContext()} 获取 Application Context。
|
||||
*/
|
||||
public abstract class BaseViewModel extends ViewModel implements IMvvmUiState {
|
||||
|
||||
// ============================================================
|
||||
// 通用 UI 状态
|
||||
// ============================================================
|
||||
|
||||
/** 全局加载状态 */
|
||||
private final MutableLiveData<Boolean> mLoadingLiveData = new MutableLiveData<>(false);
|
||||
|
||||
/** 全局错误提示 */
|
||||
private final MutableLiveData<String> mErrorLiveData = new MutableLiveData<>();
|
||||
|
||||
/** 全局成功提示 */
|
||||
private final MutableLiveData<String> mSuccessLiveData = new MutableLiveData<>();
|
||||
|
||||
// ============================================================
|
||||
// RxJava 异步管理(核心)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Google 标准写法:ViewModel 内统一管理 Rx 订阅,
|
||||
* onCleared 时自动取消,防止内存泄漏。
|
||||
* CompositeDisposable 用于统一管理所有 RxJava 订阅。
|
||||
* 在 onCleared() 中自动 dispose,防止内存泄漏。
|
||||
*/
|
||||
protected final CompositeDisposable mDisposables = new CompositeDisposable();
|
||||
|
||||
// ============================================================
|
||||
// 对外暴露(只读)
|
||||
// ============================================================
|
||||
|
||||
public LiveData<Boolean> getLoadingLiveData() {
|
||||
return mLoadingLiveData;
|
||||
}
|
||||
|
||||
public LiveData<String> getErrorLiveData() {
|
||||
return mErrorLiveData;
|
||||
}
|
||||
|
||||
public LiveData<String> getSuccessLiveData() {
|
||||
return mSuccessLiveData;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 受保护的方法(子类调用)
|
||||
// ============================================================
|
||||
|
||||
protected void setLoading(boolean loading) {
|
||||
mLoadingLiveData.postValue(loading);
|
||||
}
|
||||
|
||||
protected void setError(String message) {
|
||||
mErrorLiveData.postValue(message);
|
||||
}
|
||||
|
||||
protected void setSuccess(String message) {
|
||||
mSuccessLiveData.postValue(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将订阅托管给 ViewModel 生命周期
|
||||
* 便捷方法:将 Disposable 添加到 {@link #mDisposables} 中管理。
|
||||
* 子类在发起 RxJava 请求时调用此方法,确保 ViewModel 销毁时自动取消订阅。
|
||||
*/
|
||||
protected void addDisposable(Disposable disposable) {
|
||||
mDisposables.add(disposable);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 生命周期
|
||||
// ============================================================
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
mDisposables.clear();
|
||||
super.onCleared();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前viewmodel对应的页面binding
|
||||
*/
|
||||
protected VDB binding;
|
||||
|
||||
public void setVDBinding(ViewDataBinding vdBinding) {
|
||||
binding = (VDB) vdBinding;
|
||||
}
|
||||
|
||||
public VDB getVDBinding() {
|
||||
if (binding == null) {
|
||||
throw new NullPointerException("BaseViewModel >> getVDBinding >> null!!!");
|
||||
// ViewModel 销毁时,自动取消所有正在执行的 RxJava 任务
|
||||
if (!mDisposables.isDisposed()) {
|
||||
mDisposables.clear();
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
private WeakReference<Context> weakContext;
|
||||
|
||||
public void setContext(Context context) {
|
||||
if (weakContext == null) {
|
||||
weakContext = new WeakReference<>(context.getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
public Context getSafeContext() {
|
||||
if (weakContext == null) {
|
||||
throw new NullPointerException("BaseViewModel >> getCtx >> null!!!");
|
||||
}
|
||||
return weakContext.get();
|
||||
}
|
||||
|
||||
private BehaviorSubject<T> mBehaviorSubject;
|
||||
|
||||
public void setLifecycle(BehaviorSubject subject) {
|
||||
this.mBehaviorSubject = (BehaviorSubject<T>) subject;
|
||||
}
|
||||
|
||||
public BehaviorSubject<T> getLifecycle() {
|
||||
return mBehaviorSubject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ttstd.dialer.base.mvvm;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
/**
|
||||
* MVVM ViewModel 通用 UI 状态接口
|
||||
*
|
||||
* {@link BaseViewModel} 与 {@link BaseAndroidViewModel} 均实现本接口,
|
||||
* 使基类({@link BaseMvvmActivity} / {@link BaseMvvmFragment} / {@link BaseMvvmDialogFragment})
|
||||
* 能以统一的 {@code VM extends ViewModel} 泛型约束订阅 loading / error / success 状态。
|
||||
*/
|
||||
public interface IMvvmUiState {
|
||||
|
||||
/** 全局加载状态(只读) */
|
||||
LiveData<Boolean> getLoadingLiveData();
|
||||
|
||||
/** 全局错误提示(只读) */
|
||||
LiveData<String> getErrorLiveData();
|
||||
|
||||
/** 全局成功提示(只读) */
|
||||
LiveData<String> getSuccessLiveData();
|
||||
}
|
||||
+110
-22
@@ -6,6 +6,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -16,20 +17,35 @@ import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.ttstd.dialer.base.BaseDialogFragment;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.base.mvvm.IMvvmUiState;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
||||
/**
|
||||
* 优化生命周期管理,去掉冗余的 Bundle 定义
|
||||
* @param <VM> ViewModel 类型
|
||||
* MVVM DialogFragment 基类
|
||||
*
|
||||
* 职责:
|
||||
* - 绑定 DataBinding
|
||||
* - 初始化 ViewModel(默认通过反射自动创建)
|
||||
* - 订阅 ViewModel 中的 LiveData({@link #observeViewModel()})
|
||||
* - 订阅全局通用状态({@link #observeCommonState()})
|
||||
* - 页面创建完成后加载数据({@link #onLoadData()})
|
||||
* - 将用户操作转发给 ViewModel
|
||||
*
|
||||
* 禁止:
|
||||
* - 在 DialogFragment 中直接进行网络请求或数据库操作(必须通过 ViewModel → Repository)
|
||||
* - 持有可变 LiveData 的引用(应通过 LiveData 回调观察)
|
||||
* - 在 DialogFragment 中写业务逻辑
|
||||
*
|
||||
* @param <VM> ViewModel 类型,必须继承 {@link BaseViewModel} 或 {@link BaseAndroidViewModel}
|
||||
* @param <VDB> ViewDataBinding 类型
|
||||
*/
|
||||
public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseDialogFragment {
|
||||
|
||||
protected final String mTag = this.getClass().getSimpleName();
|
||||
|
||||
|
||||
protected boolean mIsVisible;
|
||||
protected boolean mHasPrepare;
|
||||
|
||||
@@ -48,18 +64,16 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
|
||||
ctx = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 生命周期
|
||||
// ============================================================
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// ViewModel 初始化移至 onCreate
|
||||
// ViewModel 初始化
|
||||
try {
|
||||
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
mViewModel = new ViewModelProvider(this).get(vmClass);
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
BaseViewModel<?, ?> baseVm = (BaseViewModel<?, ?>) mViewModel;
|
||||
baseVm.setContext(getContext());
|
||||
baseVm.setLifecycle(getLifecycleSubject());
|
||||
}
|
||||
mViewModel = createViewModel();
|
||||
} catch (Exception e) {
|
||||
Logger.e(mTag, "ViewModel initialization failed: " + e.getMessage());
|
||||
}
|
||||
@@ -68,10 +82,8 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
// DataBinding 初始化
|
||||
mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
((BaseViewModel<?, ?>) mViewModel).setVDBinding(mViewDataBinding);
|
||||
}
|
||||
return mViewDataBinding.getRoot();
|
||||
}
|
||||
|
||||
@@ -80,9 +92,17 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mViewDataBinding.setLifecycleOwner(getViewLifecycleOwner());
|
||||
|
||||
// 订阅通用状态
|
||||
observeCommonState();
|
||||
|
||||
// 订阅业务状态
|
||||
observeViewModel();
|
||||
|
||||
initDataBinding();
|
||||
initView(getArguments());
|
||||
initData(savedInstanceState);
|
||||
initView(savedInstanceState);
|
||||
|
||||
// 加载数据
|
||||
onLoadData();
|
||||
|
||||
mHasPrepare = true;
|
||||
if (mIsVisible || getUserVisibleHint()) {
|
||||
@@ -96,9 +116,6 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
|
||||
super.onDestroyView();
|
||||
mHasPrepare = false;
|
||||
// 及时解绑 Binding,防止内存泄漏
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
((BaseViewModel<?, ?>) mViewModel).setVDBinding(null);
|
||||
}
|
||||
mViewDataBinding = null;
|
||||
}
|
||||
|
||||
@@ -122,14 +139,85 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
|
||||
public void fetchData() {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 抽象方法(子类必须实现)
|
||||
// ============================================================
|
||||
|
||||
/** 创建 ViewModel 实例(默认通过反射自动创建,子类可按需重写) */
|
||||
protected VM createViewModel() {
|
||||
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
return new ViewModelProvider(this).get(vmClass);
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
/** 初始化 DataBinding(如设置 setClick、绑定表达式等) */
|
||||
protected abstract void initDataBinding();
|
||||
|
||||
protected abstract void initView(Bundle bundle);
|
||||
/** 初始化视图(如设置 Adapter、Listener 等) */
|
||||
protected abstract void initView(Bundle savedInstanceState);
|
||||
|
||||
protected abstract void initData(Bundle savedInstanceState);
|
||||
/** 订阅 ViewModel 中的业务 LiveData */
|
||||
protected abstract void observeViewModel();
|
||||
|
||||
// ============================================================
|
||||
// 可选重写
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 页面创建完成后加载数据
|
||||
* 子类可重写此方法触发首次数据加载
|
||||
*/
|
||||
protected void onLoadData() {
|
||||
// 默认空实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅全局通用状态(loading / error / success)
|
||||
*/
|
||||
protected void observeCommonState() {
|
||||
if (!(mViewModel instanceof IMvvmUiState)) return;
|
||||
IMvvmUiState vm = (IMvvmUiState) mViewModel;
|
||||
|
||||
vm.getLoadingLiveData().observe(getViewLifecycleOwner(), loading -> {
|
||||
if (loading != null && loading) {
|
||||
showLoading();
|
||||
} else {
|
||||
hideLoading();
|
||||
}
|
||||
});
|
||||
|
||||
vm.getErrorLiveData().observe(getViewLifecycleOwner(), error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
showError(error);
|
||||
}
|
||||
});
|
||||
|
||||
vm.getSuccessLiveData().observe(getViewLifecycleOwner(), msg -> {
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
showSuccess(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UI 辅助方法(子类可重写)
|
||||
// ============================================================
|
||||
|
||||
protected void showLoading() {
|
||||
}
|
||||
|
||||
protected void hideLoading() {
|
||||
}
|
||||
|
||||
protected void showError(String message) {
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
protected void showSuccess(String message) {
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -16,27 +17,41 @@ import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.ttstd.dialer.base.BaseFragment;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.base.mvvm.IMvvmUiState;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
||||
/**
|
||||
* 优化生命周期管理,去掉冗余的 Bundle 定义
|
||||
* @param <VM> ViewModel 类型
|
||||
* MVVM Fragment 基类
|
||||
*
|
||||
* 职责:
|
||||
* - 绑定 DataBinding
|
||||
* - 初始化 ViewModel(默认通过反射自动创建)
|
||||
* - 订阅 ViewModel 中的 LiveData({@link #observeViewModel()})
|
||||
* - 订阅全局通用状态({@link #observeCommonState()})
|
||||
* - 页面创建完成后加载数据({@link #onLoadData()})
|
||||
* - 支持懒加载({@link #onEnter()} / {@link #onExit()})
|
||||
* - 将用户操作转发给 ViewModel
|
||||
*
|
||||
* 禁止:
|
||||
* - 在 Fragment 中直接进行网络请求或数据库操作(必须通过 ViewModel → Repository)
|
||||
* - 持有可变 LiveData 的引用(应通过 LiveData 回调观察)
|
||||
* - 在 Fragment 中写业务逻辑
|
||||
* - 使用 getActivity() 强转后直接操作 Activity(应通过 ViewModel 通信)
|
||||
*
|
||||
* @param <VM> ViewModel 类型,必须继承 {@link BaseViewModel} 或 {@link BaseAndroidViewModel}
|
||||
* @param <VDB> ViewDataBinding 类型
|
||||
*/
|
||||
public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseFragment {
|
||||
|
||||
protected final String mTag = this.getClass().getSimpleName();
|
||||
|
||||
/**
|
||||
* 是否可见(用于懒加载逻辑)
|
||||
*/
|
||||
|
||||
/** 是否可见(用于懒加载逻辑) */
|
||||
protected boolean mIsVisible;
|
||||
/**
|
||||
* View 是否准备完毕
|
||||
*/
|
||||
|
||||
/** View 是否准备完毕 */
|
||||
protected boolean mHasPrepare;
|
||||
|
||||
protected VM mViewModel;
|
||||
@@ -54,18 +69,16 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
|
||||
ctx = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 生命周期
|
||||
// ============================================================
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// ViewModel 初始化
|
||||
try {
|
||||
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
mViewModel = new ViewModelProvider(this).get(vmClass);
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
BaseViewModel<?, ?> baseVm = (BaseViewModel<?, ?>) mViewModel;
|
||||
baseVm.setContext(getContext());
|
||||
baseVm.setLifecycle(getLifecycleSubject());
|
||||
}
|
||||
mViewModel = createViewModel();
|
||||
} catch (Exception e) {
|
||||
Logger.e(mTag, "ViewModel initialization failed: " + e.getMessage());
|
||||
}
|
||||
@@ -74,11 +87,8 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
// ViewDataBinding 初始化
|
||||
// DataBinding 初始化
|
||||
mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
((BaseViewModel<?, ?>) mViewModel).setVDBinding(mViewDataBinding);
|
||||
}
|
||||
return mViewDataBinding.getRoot();
|
||||
}
|
||||
|
||||
@@ -86,11 +96,18 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mViewDataBinding.setLifecycleOwner(getViewLifecycleOwner());
|
||||
|
||||
|
||||
// 订阅通用状态
|
||||
observeCommonState();
|
||||
|
||||
// 订阅业务状态
|
||||
observeViewModel();
|
||||
|
||||
initDataBinding();
|
||||
// 去掉重复的 bundle 和 savedInstanceState 定义,直接从参数/方法获取
|
||||
initView(getArguments());
|
||||
initData(savedInstanceState);
|
||||
initView(savedInstanceState);
|
||||
|
||||
// 加载数据
|
||||
onLoadData();
|
||||
|
||||
mHasPrepare = true;
|
||||
// 检查初始可见性
|
||||
@@ -105,9 +122,6 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
|
||||
super.onDestroyView();
|
||||
mHasPrepare = false;
|
||||
// 及时解绑 Binding,防止内存泄漏
|
||||
if (mViewModel instanceof BaseViewModel) {
|
||||
((BaseViewModel<?, ?>) mViewModel).setVDBinding(null);
|
||||
}
|
||||
mViewDataBinding = null;
|
||||
}
|
||||
|
||||
@@ -127,14 +141,91 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 抽象方法(子类必须实现)
|
||||
// ============================================================
|
||||
|
||||
/** 创建 ViewModel 实例(默认通过反射自动创建,子类可按需重写) */
|
||||
protected VM createViewModel() {
|
||||
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
return new ViewModelProvider(this).get(vmClass);
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
/** 初始化 DataBinding(如设置 setClick、绑定表达式等) */
|
||||
protected abstract void initDataBinding();
|
||||
|
||||
protected abstract void initView(Bundle bundle);
|
||||
/** 初始化视图(如设置 Adapter、Listener 等) */
|
||||
protected abstract void initView(Bundle savedInstanceState);
|
||||
|
||||
protected abstract void initData(Bundle savedInstanceState);
|
||||
/** 订阅 ViewModel 中的业务 LiveData */
|
||||
protected abstract void observeViewModel();
|
||||
|
||||
// ============================================================
|
||||
// 可选重写
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 页面创建完成后加载数据
|
||||
* 子类可重写此方法触发首次数据加载
|
||||
*/
|
||||
protected void onLoadData() {
|
||||
// 默认空实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅全局通用状态(loading / error / success)
|
||||
* 子类可在 {@link #observeViewModel()} 中调用 super.observeCommonState(),或直接重写
|
||||
*/
|
||||
protected void observeCommonState() {
|
||||
if (!(mViewModel instanceof IMvvmUiState)) return;
|
||||
IMvvmUiState vm = (IMvvmUiState) mViewModel;
|
||||
|
||||
vm.getLoadingLiveData().observe(getViewLifecycleOwner(), loading -> {
|
||||
if (loading != null && loading) {
|
||||
showLoading();
|
||||
} else {
|
||||
hideLoading();
|
||||
}
|
||||
});
|
||||
|
||||
vm.getErrorLiveData().observe(getViewLifecycleOwner(), error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
showError(error);
|
||||
}
|
||||
});
|
||||
|
||||
vm.getSuccessLiveData().observe(getViewLifecycleOwner(), msg -> {
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
showSuccess(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UI 辅助方法(子类可重写)
|
||||
// ============================================================
|
||||
|
||||
protected void showLoading() {
|
||||
// 默认实现:可替换为自定义 LoadingDialog
|
||||
}
|
||||
|
||||
protected void hideLoading() {
|
||||
}
|
||||
|
||||
protected void showError(String message) {
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
protected void showSuccess(String message) {
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 懒加载回调(子类可重写)
|
||||
// ============================================================
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
@@ -150,4 +241,17 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
|
||||
public boolean isAttached() {
|
||||
return getCtx() != null && isAdded();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 禁止使用的模式(编译期约束)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* @deprecated 禁止在 MVVM Fragment 中通过 getActivity() 强转后直接操作 Activity
|
||||
* → 应通过 ViewModel 通信
|
||||
*/
|
||||
@Deprecated
|
||||
protected android.app.Activity getActivityAs() {
|
||||
throw new UnsupportedOperationException("禁止在 MVVM Fragment 中使用 getActivity() 强转");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user