MVVM纯javabase类分享

335 阅读6分钟

引言

作为一名安卓的开发人员,对各种架构应该是非常熟悉的:MVC,MVP,MVVM;包括现在留下的MVI,这些都是前人的智慧的结晶,都有各自的应用场景。 作为一名目前技术一般的开发人员,我们这里不去分析整个架构的流程,功能点等;直接贴一下自己使用的base类,需要的人可以直接CV获取。

PS:

这里为了不写findViewById,直接集成了DataBinding,需要加入如下依赖:

dataBinding {
    enabled = true;
}

BaseRepository

public class BaseRepository {
}
  • repository作为model层,负责与底层(相对于ui来说的)进行交互,如:网络请求,本地数据查询等。
  • 我一般会在这里使用rxjava+retrofit进行网络请求,在数据请求返回时,通过接口或者LiveDataBus将数据回调到viewmodel中。
  • 这里比较推荐构造一个工厂,来生产不同情况下需要的repository。
  • 仓库工厂类如下:
public class RepositoryFactory {
    private static RepositoryFactory sInstance;

    private RepositoryFactory() {
    }

    @NonNull
    public static RepositoryFactory getInstance() {
        if (sInstance == null) {
            sInstance = new RepositoryFactory();
        }
        return sInstance;
    }

    private HashMap<String, BaseRepository> mRepositoryMap = new HashMap<>();

    /**
     * 获取对应的repository对象 如果有就用之前的 没有就创一个新的
     */
    public <T extends BaseRepository> T getRepository(@NonNull Class<T> repositoryClass) {
        String canonicalName = repositoryClass.getCanonicalName();
        if (mRepositoryMap.containsKey(canonicalName)) {
            return (T) mRepositoryMap.get(canonicalName);
        } else {
            BaseRepository repository = create(repositoryClass);
            mRepositoryMap.put(canonicalName, repository);
            return (T) repository;
        }
    }

    /**
     * 使用默认构造创建一个新的
     */
    @NonNull
    private <T extends BaseRepository> T create(@NonNull Class<T> repositoryClass) {
        try {
            return repositoryClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + repositoryClass, e);
        }
    }
}

BaseViewModel

public abstract class BaseViewModel<K extends BaseRepository> extends ViewModel {
    protected K mRepository;

    public BaseViewModel() {
        init();
    }

    protected void init() {
        mRepository = requireRepository();
    }

    /**
     * 返回对应的仓库实例 如: BaseRepository对象
     * return RepositoryFactory.getInstance().getRepository(AccountRepository.class);
     */
    protected abstract K requireRepository();
}
  • viewModel用于数据处理:例如用户点击了界面的button用于登录,就需要在viewModel去封装对应的如用户名、密码等数据,并使用repository进行数据请求;又比如,请求歌单数据,后端返回了很复杂的数据,而我们的界面中,并不需要这么复杂的数据(比如返回数据有三十个字段,界面需要的可能就四五个),这样我们就可以在数据返回时,做一定的处理
  • --当然,这种情况我还是使用的比较少的,一般都是直接返回到ui层...
  • 这里的viewmodel,需要传入一个repository的泛型,与之绑定。

BaseActivity

public abstract class BaseActivity<T extends ViewDataBinding, V extends BaseViewModel> extends AppCompatActivity {
    protected T mDataBinding;
    protected V mViewModel;

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

        mDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
        mViewModel = getViewModel();

        initView();
        initListener();
        initObserver();
    }

    /**
     * 初始化界面组件
     */
    protected abstract void initView();

    /**
     * 初始化监听之类
     */
    protected abstract void initListener();

    /**
     * 初始化 观察者监听
     */
    protected abstract void initObserver();

    /**
     * 使用view model provider 构造view model
     */
    protected abstract V getViewModel();

    /**
     * 返回布局id
     */
    protected abstract int getLayoutId();
  • BaseActivity需要传入对应的dataBinding泛型和viewmodel的泛型,viewmodel泛型如前文的repository一样;而dataBinding的泛型,需要你在引入databinding的依赖后,在布局文件中,使用alt+enter,选择convertxxx的来转换成dataBinding对应的布局,这样就会生产对应的Binding类,Binding类的名字是根据你的布局的名字来的:如activity_main对应的是ActivityMainBinding。
  • 除此以外,你还需要在你的getLayoutId()方法中,返回你自己的布局id,这样,当activity运行起来时,就会自动显示对应的布局了,不需要手动编写setContentView()方法和findViewById了。
  • getViewModel()方法需要返回一个viewModel对象,这个返回的就是后面实际上操作的对象。

BaseFragment

public abstract class BaseFragment<T extends ViewDataBinding, V extends BaseViewModel> extends Fragment {
    protected T mDataBinding;
    protected V mViewModel;
    protected Context mContext;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
        mContext = requireContext();
        mViewModel = getViewModel();
        initView();
        initListener();
        initObserver();
        mDataBinding.setLifecycleOwner(this);
        return mDataBinding.getRoot();
    }

    /**
     * 初始化界面组件
     */
    protected abstract void initView();

    /**
     * 初始化监听之类
     */
    protected abstract void initListener();

    /**
     * 初始化 观察者监听
     */
    protected abstract void initObserver();

    /**
     * 使用view model provider 构造view model
     */
    protected abstract V getViewModel();

    /**
     * 返回对应的布局id
     */
    protected abstract int getLayoutId();

    public void toFragment(@NonNull BaseFragment fragment) {
        MainActivity activity = (MainActivity) requireActivity();
        if (activity != null) activity.toFragment(fragment);
        else LogUtils.e("require activity error...");
    }
  • 就我自己的开发过程中来说,写fragment还是比activity多的多,比较喜欢多一些fragment少activity的方式。
  • 对应的需要进行修改的地方,与BaseActivity一致。

使用例子:

  • 纸上得来终觉浅,绝知此事要躬行。
  • 看了这么多,不如直接实操一下。阅读者可以直接手动粘贴上方的base类到项目中,接着就可以试一下下方的例子。 构建“仓库”:
  • 我们有很多种方式从repository返回数据到viewModel:接口、eventbus等。这里我选择常见的接口进行数据返回,为了书写方便,减少重复代码,我选择修改一下base类:
public class BaseRepository<T extends BaseRepository.BaseCallback> {

    protected T callback;

    public void setCallback(T baseCallback) {
        this.callback = baseCallback;
    }

    public interface BaseCallback {

    }
}

构建仓库类:

public class MainRepository extends BaseRepository<MainRepository.OnRequestBaseCallback> {
    /**
     * 请求数据
     */
    public void requestData() {
        // 这里延时操作来模拟数据返回
        new Thread(() -> {
            // 这里模拟请求数据返回
            List<String> data = new ArrayList<String>() {{
                add("张三");
                add("李四");
                add("王五");
            }};

            if (callback != null) {
                callback.onUserCallback(data);
            }
        }).start();
    }

    public interface OnRequestBaseCallback extends BaseCallback {
        void onUserCallback(@NonNull List<String> data);
    }
}

这里我们创建了一个MainRepository,请求数据,返回一个String类型的list,并当接受到返回的数据时,通过接口进行回调。

构建viewModel

```
public class MainViewModel extends BaseViewModel<MainRepository> {

    private MutableLiveData<List<String>> userList = new MutableLiveData<>();

    public MutableLiveData<List<String>> getUserList() {
        return userList;
    }

    @Override
    protected void init() {
        super.init();

        mRepository.setCallback(data -> {
            userList.setValue(data);
        });
    }

    /**
     * 请求数据
     */
    public void requestData(){
        mRepository.requestData();
    }

    @Override
    protected MainRepository requireRepository() {
        return RepositoryFactory.getInstance().getRepository(MainRepository.class);
    }
}
```

viewmodel中使用MutableLiveData+泛型的方式声明数据,当数据返回时,只需要将数据同步到MutableLiveData中即可。

构建fragment

public class MainFragment extends BaseFragment<LayoutFragmentMainBinding, MainViewModel> {
    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void initView() {
    }


    @Override
    protected void initListener() {
        // 测试请求数据
        mDataBinding.topBar.setOnClickListener(v -> mViewModel.requestData());
    }

    @Override
    protected void initObserver() {
        mViewModel.getUserList().observe(this, strings -> {
            // todo 在这里接收到数据返回 更新ui
        });
    }

    @Override
    protected MainViewModel getViewModel() {
        return new ViewModelProvider(requireActivity()).get(MainViewModel.class);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.layout_fragment_main;
    }
}

结束

上述就是一个完整的,我平时常用的一个可以快速构建的mvvm模板,希望对读者可以带来一定的帮助。

补充BaseView

  • 相信读者都对自定义有自己独到的见解,诚然,你当然可以自己写一个view来继承view或者viewgroup,根据自己的逻辑重写其中的measure、layout、draw方法,来达到自己想要的效果。
  • 但就我自己而言,有时候这样可能不够快。例如有这样一个场景,我需要做一个类似卡片的效果,它有自己需要的布局:背景是什么样的,距离左上角多少距离有尺寸多少的文字1,其他地方有文字2。这是一个很常见的场景。
  • 于是我就想,要是可以直接写一个布局就好了。

BaseView

public abstract class BaseView<T extends ViewDataBinding> extends FrameLayout {
    protected T mDataBinding;
    protected Context mContext;

    public BaseView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mDataBinding = DataBindingUtil.inflate(LayoutInflater.from(context), getLayoutId(), this, false);
        addView(mDataBinding.getRoot());
        init(attrs);
    }

    public T getDataBinding() {
        return mDataBinding;
    }

    protected abstract void init(AttributeSet attrs);

    protected abstract int getLayoutId();

}

泛型的返回类型和前文的activity和fragment类似。

真·结语

  • 上文只是我个人平时使用的模板,它不一定是最好的,也会有一些问题,但却是目前来说我自己使用的比较习惯的一套代码。发出来只是希望可以对读者带来一定的帮助。
  • 毕竟,我也吃了很多前人种下来的瓜;要是可以让后人吃我种的瓜的时候,不吐槽这个太难吃,我就比较满意了。