集成MVVMHabit开源框架的常见问题

在学习了最火的安卓Mvvm架构模式之后,我们想通过远程的方式来集成mvvmhabit这个框架,下面就是我遇到的一些集成问题,希望对大家有用。
在这里插入图片描述

../../../.gradle/caches/transforms-1/files-1.1/appcompat-v7-28.0.0.aar/369205b9ef94f35037ab9e659bb394fa/res/values/values.xml	
error: resource android:attr/ttcIndex not found.	

后来发现是因为我们项目的编译版本和mvvmhabit框架的版本不一致导致的,改成下面的编译版本,统一mvvmhabit的编译版本一样。

  compileSdkVersion: 28,
  buildToolsVersion: "28.0.0",

解决了这个问题,我们就成功引入了mvvmhabit这个框架了,现在我们要做的就是在不改变之前业务逻辑的情况下引入这个框架。首先,我们需要解决的就是各种BaseActivity 、BaseFragment、还有Application。让这些底层的老框架兼容这个新的mvvm框架之后,我们再去改业务层面的逻辑代码,改成mvvm的架构模式。业务层的代码重构,可以根据模块划分,主要就是要让view和ViewModel之间实现双向的绑定,然后再去更改model和model间的双向绑定。
首先,我们来更改BaseActivity,之前我们的BaseActivity可能都是长成这样的:

public abstract class FrameActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(initLayout() >0) {
            setContentView(initLayout());
        }
        initView();
        initData();
        initListener();

    }
    protected abstract int initLayout();
    protected abstract void initView();
    protected abstract void initData();
    protected abstract void initListener();
    }

这个抽象父类抽象了公共的一些方法,然后放在了onCreate()方法里面依次调用。但是现在我们引入mvvmhabit这个框架之后,我们发现这个框架已经给我提供了BaseActivity,而且这个BaseActivity不能再给我们二次封装了,因为这个mvvmhabit这个框架的BaseActivity需要子类直接传入绑定的xml布局,这个布局是根据你在布局文件用<layout></layout>标签包裹布局之后自动生成的去掉文件下划线,首字母大写并在后面加上Banding的一个Java类,点击这个类可以直接跳转到你的布局文件。
其实mvvm架构虽然好用,但是也是有弊端的,就像是SplashActivity这个类,虽然它也是有界面的,但是它并不需要有用户进行交互,只是简单的展示一下企业的Slogen页面,然后就跳转到了首页。但是为了统一框架结构,我们还是选择让它继承mvvmhabit的BaseActivity.
最后除了需要些一个多余的SplahModel在activity里面传过去外,其他的都一切正常,但是运行之后报下面的这个错误:

 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.fenjiread.youthtoutiao/com.youth.toutiao.SplashActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x1

我的SplashActivity找不到资源文件,这个让我很纳闷,我的代码里面根本就没有操作资源文件的代码,为什么报资源文件找不到呢?
后面发现是因为我把覆盖的两个方法的返回参数弄混了,返回的参数不是布局文件导致的。正确的代码如下图:

class SplashActivity : BaseActivity<AstivitySplashBinding, SplashModel> {

    constructor(){}

    override fun initVariableId(): Int {
        return BR.viewModel
    }

    override fun initContentView(savedInstanceState: Bundle?): Int {
        return R.layout.astivity_splash
    }

    override fun initData() {
        Timer().schedule(object : TimerTask() {
            override fun run() {
                val isLogin = SPUtils.getInstance().getBoolean(ConstantConfig.IS_LOGIN_IN)
                if (isLogin) {
                    val intent = Intent(this@SplashActivity, MainActivity::class.java)
                    startActivity(intent)
                    //保存一个登录时间
                    SPUtils.getInstance().put(ConstantConfig.LOGIN_TIME,System.currentTimeMillis())
                    finish()
                    isLoginOneDay()
                } else {
                    val intent = Intent(this@SplashActivity, LoginRegisterActivity::class.java)
                    startActivity(intent)
                    finish()
                }
            }
        }, 3000)
    }
}

这里继承自BaseActivity之后,必须要子类重写一个子类的构造方法,否则就会报编译错误,提示父类需要在子类实例化。
还有子Activity继承mvvmhabit框架的BaseActivity传递第一个参数布局绑定类自动生成之后,在子Activity里面无法识别出来。经常性的出现下面这样的问题。

class LoginRegisterActivity : BaseActivity<com.fenjiread.youthtoutiao.databinding.ActivityLoginBinding, LoginViewModel> {

第一个参数可以自动导包,但是报红说解析不了,这就可能是你写到布局文件之后,自动生成的banding文件没有办法使用,尝试一下“clean project”或者“rebuild project”来解决问题,但是还是无法真正解决问题。
后来发现是因为我把xml布局的data文件的类型和名字写反了,正确的写法应该是下面这样:

    <data>
        <variable
            name="loginViewModel"
            type="com.youth.toutiao.viewmodel.LoginViewModel"/>
    </data>

不能把name和type写反了,否则就会导致编译的Banding类自动生成的但是解析不了,无法识别,说白了还就是细心的问题。
现在继续重构发现,如果采用mvvm重构项目,那么我们之前无论是采用的是mvc还是mvp架构,重构成mvvm架构模式时,需要把之前在activity里面实现的逻辑全部转移到了对应的界面ViewModel,然后通过对界面的控件添加监听事件,当用户操作的时候,对应的触发ViewModel的事件监听,然后获取到数据之后再更新界面。
绑定用户名:
在LoginViewModel中定义

//用户名的绑定
public ObservableField<String> userName = new ObservableField<>("");

在用户名EditText标签中绑定

android:text="@={viewModel.userName}"

这样一来,输入框中输入了什么,userName.get()的内容就是什么,userName.set("")设置什么,输入框中就显示什么。
注意: @符号后面需要加=号才能达到双向绑定效果;userName需要是public的,不然viewModel无法找到它。
点击事件绑定:
在LoginViewModel中定义

//登录按钮的点击事件
public View.OnClickListener loginOnClick = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
            
    }
};

在登录按钮标签中绑定

android:onClick="@{viewModel.loginOnClick}"

注意:点击事件无法双向绑定,否则编译报错。
这样一来,用户的点击事件直接被回调到ViewModel层了,更好的维护了业务逻辑 。
如果你之前的项目采用了Kotlin语言来开发项目,继承BaseViewModel的时候需要传递一个具体的数据实体类,但是你发现用Java语言是不需要的。
实践mvvm架构模式之后发现,其实我们的View层其实不仅仅是指Xml,而且还有Activity的findViewById()的操作其实也是属于View层的,所以如果我们想快速的集成使用mvvmhabit这个框架,那我们可以先展示让activity的代码存在,先创建出与activity对应的ViewModel,里面只是一个空实现,就像下面的代码。

class LoginViewModel : BaseViewModel<PwdLoginParam> {

    constructor(application: Application) : super(application) {}

    constructor(application: Application, model: PwdLoginParam) : super(application, model) {}

    var mPhoneNumber = ObservableField("")
    var mPassword = ObservableField("")
    var mLoginClickListener = View.OnClickListener{
        val phoneNumber = mPhoneNumber.get()
        val password = mPassword.get()
        Log.e(">>>>>>>>>>>>>", "phoneNumber:" + phoneNumber!!)
        Log.e(">>>>>>>>>>>>>", "password:" + password!!)
    }
}

如果你的项目是kotlin项目,那么就需要传入一个继承自BaseModel的数据实体类,然后在让activity继承BaseActivity就可以实现mvvm架构模式了。这种继承方式在不改变原有的代码的情况下,让之前在activity里面findViewById的代码保持原样,只是先搭建了一个data banging的架子,等程序运行没有问题之后,我们再一点点把之前放在activity里的业务逻辑慢慢迁移到activity对应的ViewModel里面,从而实现界面也数据之间的绑定。
那么问题来了,我们可不可以直接在activity里面创建数据的监听对像,然后再设置到xml文件当中呢?
因为事件监听对象是通过new 出来的,那么毫无疑问我们是可以在activity里面创建界面监听对象的,测试监听对象直接设置给xml发现我们没有data里面的ViewModel引用,所以是无法把界面监听直接设置给xml的。
那么问题就来了,我们把之前在activity里面通过findViewById的业务逻辑代码,现在通过在ViewModel里面设置数据监听器和点击事件监听器来实现了。那么就必然会导致activity里面的代码减少,ViewModel里面的代码极速增长,而且如果我们需要对界面的操作,那么所谓的双向绑定该怎么去操作xml布局文件呢?
虽然mvvmhabit提供的Fragment数据绑定和Activity类似,但是复写的提供布局文件的方法还是不一样的:

class MineFragment : BaseFragment<FragmentMineBinding, MineFragmentViewModel>(), MineImple {
    override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int {
        return R.layout.fragment_mine
    }

然后你会发现,这里你没有办法在Fragment里面通过创建一个控件,然后再进行下面的findViewById方法了。

  fun initView() {
        mMineView = View.inflate(context, R.layout.fragment_mine, null)
        mBtnExitLogin = mMineView.findViewById(R.id.btn_exit_login)
        mRlCleanCache = mMineView.findViewById(R.id.rl_clean_cache)
        mTvCacheSize =  mMineView.findViewById(R.id.tv_cache_size)
    }

因为你创建的这个View本身和你通过复写initContentView传递的View对象并不是同一个对象。所以你能看到布局文件,但是布局的操作已经不受你之前的代码控制了。
那么我们就只能把业务逻辑代码从Fragment转移到ViewModel里面,在ViewModel里面初始化数据和进行界面控件的事件绑定。
我们会发现以前的findViewById不需要再写了,也不需要再activity里面根据条件来显示和隐藏一个控件或者布局了,现在我们把所有跟xml界面相关的控件都放在了ViewModel层,但是Activity里面需要怎么什么操作呢?这就是需要说到一个mvvmhabit的生命周期方法了,这个方法名为initViewObservable() ,这个方法我们移步到“最火开源框架MVVMHabit的简单集成使用“ 可以发现,这个方法是在activity初始化完毕,执行initData()方法之后执行。
因为我们的activity继承BaseActivity之后,里面有ViewModel的引用,所以我们可以直接在这个方法里面去实现一些需要资源文件的方法,或者是activity上下文,或者是界面跳转等逻辑代码都是需要在activity的容器内进行的。

 @Override
    public void initViewObservable() {
        //监听ViewModel中pSwitchObservable的变化, 当ViewModel中执行【uc.pSwitchObservable.set(!uc.pSwitchObservable.get());】时会回调该方法
        viewModel.pSwitchEvent.observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(@Nullable Boolean aBoolean) {
                //pSwitchObservable是boolean类型的观察者,所以可以直接使用它的值改变密码开关的图标
                if (viewModel.pSwitchEvent.getValue()) {
                    //密码可见
                    //在xml中定义id后,使用binding可以直接拿到这个view的引用,不再需要findViewById去找控件了
                    binding.ivSwichPasswrod.setImageResource(R.mipmap.show_psw);
                    binding.etPassword.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
                } else {
                    //密码不可见
                    binding.ivSwichPasswrod.setImageResource(R.mipmap.show_psw_press);
                    binding.etPassword.setTransformationMethod(PasswordTransformationMethod.getInstance());
                }
            }
        });
    }

所以我们现在把问题都解决了,第一不用担心ViewModel代码会过于庞杂,因为ViewModel的只能有限,它没有activitty的上下文环境和无法获取资源文件。第二就是ViewModel并不需要去拿到xml的某个控件进行操作,而是通过给xml控件设置观察者对象的方式,来监听控件的状态变化,如果需要更改UI界面的,只是文字显示就可以直接在ViewModel层处理,但是需要获取系统资源或者进行Intent跳转的逻辑代码就需要像上面一样给ViewModdel的观察者再设置一个观察者,通过观察状态的改变去更改Ui或者进行界面跳转。
那么现在还有一个问题就是,既然ViewModel和View之间是双向绑定,那么在ViewModel层可以获取到activity对象进行操作吗?
我们发现BaseViewModel里面没有直接拿到activity对象引用,而是通过eventBug发送消息的方法来进行startActivty操作,获取资源文件的方法还是需要方法activityl里面。
还有一个问题就是在Activity里面如何不通过findViewById的方式取获取到一个控件?
我们在xml给控件设置好ID之后,我们并没有在ViewModel里面去获取这些控件对象,而是如下面的代码一样,通过在activity里面获取bind对象,然后获取到控件ID就是控件本身。

 <android.support.v4.view.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            binding:adapter="@{adapter}"
            binding:itemBinding="@{viewModel.itemBinding}"
            binding:items="@{viewModel.items}"
            binding:onPageSelectedCommand="@{viewModel.onPageSelectedCommand}"
            binding:pageTitles="@{viewModel.pageTitles}" />

我们在activity里面通过下面这种方式获取到控件:

    @Override
    public void initData() {
        // 使用 TabLayout 和 ViewPager 相关联
        binding.tabs.setupWithViewPager(binding.viewPager);
        binding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabs));
        //给ViewPager设置adapter
        binding.setAdapter(new ViewPagerBindingAdapter());
    }

通过在xml先对控件进行binding操作之后,我们就可以在原activity里面更改之前的findViewById()操作了。
目前为止,我们已经知道如何更改原有项目成Mvvm架构模式了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值