集成LiveData+ViewModel+Retrofit+Room的一个Demo

集成LiveData+ViewModel+Retrofit+Room的一个Demo

2018年Google的IO大会提出来了Jetpack,上面的一些技术点都是这个里面的,正好公司新开了一个项目,所以抱着尝尝鲜的态度来感受一下(内容有点多,基本上都是代码)。

主要技术点

  • LiveData
  • ViewModel
  • Retrofit+RxJava
  • Room
  • 注:本Demo是采用的androidx(大趋势,虽然会出现很多兼容性问题)

这里就不多讲这些技术的优点,Google和百度上面一堆的介绍。
这个demo的主要功能就是登陆并且将从服务器获取的数据存到本地数据库然后读取出来(还是很简单的)。后台服务器是用的我之前写过的一个SpringBoot小项目,感兴趣的地址

接口内容

在这里插入图片描述
参数是以json的格式放入body然后post提交的

封装网络请求模块

这里网络请求是采用的Retrofit+RxJava2

/**
 * api商店
 */
public interface ApiStore {

    @POST("user/login")
    Flowable<BaseResult<UserEntity>> login(@Body RequestBody body);
}

然后对retrofit和okhttp进行初始化


public class RetrofitClient {
    private static final String TAG="client";
    private static OkHttpClient okHttpClient;
    private static Retrofit retrofit;

    public static ApiStore getInstance(){
        if (okHttpClient==null){

            ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(MyApplication.getContext()));

            okHttpClient=new OkHttpClient.Builder()
                    .addInterceptor(new HttpLoggingInterceptor(msg-> {
                        //访问网络请求,和服务端响应请求时。将数据拦截并输出
                            Log.e(TAG, "log: " + msg);

                    }).setLevel(HttpLoggingInterceptor.Level.BODY))
                    .connectTimeout(NetConstant.TIME_OUT, TimeUnit.SECONDS)
                    .readTimeout(NetConstant.TIME_OUT, TimeUnit.SECONDS)
                    .writeTimeout(NetConstant.TIME_OUT, TimeUnit.SECONDS)
                    .cookieJar(cookieJar)
                    .build();
        }  if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(NetConstant.BASE_URL)         //BaseUrl
                    .client(okHttpClient)                       //请求的网络框架
                    .addConverterFactory(GsonConverterFactory.create())     //解析数据格式
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 使用RxJava作为回调适配器
                    .build();
        }

        return retrofit.create(ApiStore.class);
    }
}

然后编写一个API管理类,负责处理这些方法

public class ApiManager {

    public static Flowable<BaseResult<UserEntity>> login(LoginParams params){
        return RetrofitClient.getInstance().login(NetUtils.ParamsToBody(params));
    }
}

那个NetUtils工具类是负责把一些参数转换成我们需要Requestbody 。BaseResult是json数据的外层,这里就不列出来了。
接下来对RxJava进行封装一下吧,让它变得更加好用一点

public class BaseHttpSubscriber<T> implements Subscriber<BaseResult<T>> {

    private ApiException apiException;

    private MutableLiveData<BaseResult<T>> data;

    public BaseHttpSubscriber(){
        data= new MutableLiveData();
    }

    public MutableLiveData<BaseResult<T>> get(){
        return data;
    }



    public void set(BaseResult<T> d){
        data.setValue(d);
    }



    @Override
    public void onSubscribe(Subscription s) {
            s.request(1);
    }

    @Override
    public void onNext(BaseResult<T> tBaseResult) {
        if (tBaseResult.getCode()== NetConstant.SUCCESS_CODE){
            onFinish(tBaseResult);
        }else {
            apiException= ExceptionEngine.handleException(new ServerException(tBaseResult.getCode(),tBaseResult.getMessage()));
            getErrorResult(apiException);
        }
    }

    @Override
    public void onError(Throwable t) {

    }

    @Override
    public void onComplete() {

    }

    public void onFinish(BaseResult<T> t){
        set(t);
    }

    private void getErrorResult(ApiException e){
        BaseResult baseResult=new BaseResult();
        baseResult.setCode(e.getCode());
        baseResult.setMessage(e.getMessage());
        onFinish(baseResult);
    }
}

这里主要是首先对数据进行拦截处理,判断code是否合法,因为我们要通过LiveData来进行数据回调处理,所以这需要把数据包装成LiveData形式通过使用setValue设置LiveData数据观察者那边就会收到对应通知。
我们数据获取都是通过Repository去拿的,所以这里需要封装一下BaseRepository


public class BaseRepository<T> {
    private BaseHttpSubscriber<T> baseHttpSubscriber;
    private Flowable<BaseResult<T>> flowable;

    public BaseHttpSubscriber<T> send(){
        flowable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(baseHttpSubscriber);
        return baseHttpSubscriber;

    }

    /**
     * 初始化
     */
    public BaseRepository() {
        baseHttpSubscriber = new BaseHttpSubscriber<>();
    }

    public BaseRepository<T> request(Flowable<BaseResult<T>> flowable) {
        this.flowable = flowable;
        return this;
    }
}

Room

我们拿到数据后肯定是要存到本地的,这里采用Room,这个也是需要我们进行一下配置的。
Room是可以结合LiveData和RxJava一起使用的,毕竟全家桶,这点还是可以的,相比之下GreenDao还得自己去处理才能使用LiveData。


@Entity
public class UserEntity {

    @PrimaryKey(autoGenerate = true)
    private int id;
    /**
     * user_id : 1000
     * username : 盖伦
     * password : 1314520
     * sex : 0
     * nickname : 德玛西亚之力
     * phone : 15575818133
     * token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAwMCwiZXhwIjoxNTU1NTU1NjU1fQ.SpKwXt_9be2UcSP3dXGj5zIBquwIsi5carEHtqAH9QQ
     * role_id : 1
     * leave : 1
     */
    private int user_id;

    private String username;
    private String password;
    private int sex;
    private String nickname;
    private String phone;
    private String token;
    private int role_id;
    private int leave;

    public int getUser_id() {
        return user_id;
    }

    public void setUser_id(int user_id) {
        this.user_id = user_id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public int getRole_id() {
        return role_id;
    }

    public void setRole_id(int role_id) {
        this.role_id = role_id;
    }

    public int getLeave() {
        return leave;
    }

    public void setLeave(int leave) {
        this.leave = leave;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "user_id=" + user_id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                ", nickname='" + nickname + '\'' +
                ", phone='" + phone + '\'' +
                ", token='" + token + '\'' +
                ", role_id=" + role_id +
                ", leave=" + leave +
                '}';
    }
}

加上@Entity这个注解,它就会对应生成这些字段
@PrimaryKey这个一定是要加的,主键
接下来编写Dao类,里面主要是添加一些我们自己需要的方法,CRUD之类的

@Dao
public interface UserDao {
  @Insert
  void insert(UserEntity entity);

  @Query("SELECT * FROM UserEntity WHERE id=1")
  UserEntity getUserOne();

  @Update
  void update(UserEntity entity);

  @Query("SELECT * FROM UserEntity ")
  List<UserEntity> getUser();

  @Query("SELECT * FROM UserEntity")
  Flowable<UserEntity> getUserByRxJava();

  @Query("SELECT * FROM UserEntity WHERE id=1")
  LiveData<UserEntity> getUserByLiveData();
}

最后两个方法是一个采用rxjava一个是livedata。
然后编写一个UserDataBases,通过这个来初始化数据库。必须要抽象类…


@Database(entities = {UserEntity.class}, version = 1)
public abstract class UserDataBases  extends RoomDatabase {

    private static volatile  UserDataBases dataBases;

    private static final String DATA_TABLE_NAME="userdb";

    private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>();
    public abstract UserDao userDao();


    public static UserDataBases getInstance(){
        if (dataBases==null){
            synchronized (UserDataBases.class){
                if (dataBases==null){
                    dataBases=buildDatabase(MyApplication.getContext(), MyApplication.getAppExecutors());
                    dataBases.updateDatabaseCreated(MyApplication.getContext().getApplicationContext());
                }
            }
        }
        return dataBases;
    }

    /**
     * Build the database. {@link Builder#build()} only sets up the database configuration and
     * creates a new instance of the database.
     * The SQLite database is only created when it's accessed for the first time.
     */
    private static UserDataBases buildDatabase(final Context appContext,
                                             final AppExecutors executors) {
        return Room.databaseBuilder(appContext, UserDataBases.class, DATA_TABLE_NAME)
                .addCallback(new Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);
                 
                    }
                })
                .build();
    }
    
    /**
     * Check whether the database already exists and expose it via {@link #getDatabaseCreated()}
     */
    private void updateDatabaseCreated(final Context context) {
        if (context.getDatabasePath(DATA_TABLE_NAME).exists()) {
            setDatabaseCreated();
        }
    }

    public LiveData<Boolean> getDatabaseCreated() {
        return mIsDatabaseCreated;
    }

    private void setDatabaseCreated(){
        mIsDatabaseCreated.postValue(true);
    }
}

最后room这快差不多搞定了,接下来编写和我们业务逻辑有关的东西,开始不是编写了一个仓库基类嘛,现在我们需要编写它的实现类。先写个接口,声明一些需要的方法

public interface ILoginRepository {
    LiveData<BaseResult<UserEntity>> login(LoginParams params);
}


public class LoginRepository extends BaseRepository<UserEntity> implements ILoginRepository {

    private UserDataBases mDatabases;

    public LoginRepository() {
        mDatabases=UserDataBases.getInstance();
    }

    @Override
    public MutableLiveData<BaseResult<UserEntity>> login(LoginParams params) {
        return request(ApiManager.login(params)).send().get();
    }

    public void insertUser(UserEntity entity){
        mDatabases.userDao().insert(entity);
    }


    public void updateUser(UserEntity entity){
        mDatabases.userDao().update(entity);

    }

    public UserEntity getUserOne(){
        return mDatabases.userDao().getUserOne();
    }

    public LiveData<UserEntity>getUserByLiveData(){
        return mDatabases.userDao().getUserByLiveData();
    }
}

这里没啥好说的,都是对数据的一些处理,登录获取数据和操作数据库。
我们的一些业务逻辑操作都交给ViewModel来处理,有点类似于MVP中的P哦,

public class UserModel extends ViewModel {

    public LoginRepository mLoginRepository;

    private MutableLiveData<BaseResult<UserEntity>> mutableLiveData; 
    private MutableLiveData<UserEntity> userEntityMutableLiveData;

    public UserModel(){
        mLoginRepository=new LoginRepository();
        userEntityMutableLiveData=new MutableLiveData<>();

    }

    public LiveData<BaseResult<UserEntity>> getUserLiveData(){
        return mutableLiveData;
    }

    public LiveData<UserEntity> getUserEntityData(){
        return userEntityMutableLiveData;
    }


    public void login(String phone, String pwd){
        LoginParams params=new LoginParams();
        params.setPhone(phone);
        params.setPassword(pwd);
        this.mutableLiveData=mLoginRepository.login(params);
    }

    public void insertUser(UserEntity userEntity){
        MyApplication.getAppExecutors().diskIO().execute(new Runnable() {
            @Override
            public void run() {
                userEntity.setId(1);

                UserEntity userOne = mLoginRepository.getUserOne();
                if (userOne==null){
                    mLoginRepository.insertUser(userEntity);
                }else {
                    mLoginRepository.updateUser(userEntity);

                }
            }
        });
    }
}

我们在insert数据的时候是开启了一个线程来进行操作的,当然也可以使用rxjava,先判断数据库中是否有数据,有就更新,没有就插入。

最后就是Activity了,

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.login_user_num_et)
    AppCompatEditText mPhoneText;
    @BindView(R.id.login_pwd_num_et)
    AppCompatEditText mPwdText;

    private UserModel mModel;
    Observer<BaseResult<UserEntity>> observer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mModel=ViewModelProviders.of(this).get(UserModel.class);

       observer =new Observer<BaseResult<UserEntity>>() {
            @Override
            public void onChanged(BaseResult<UserEntity> userEntityBaseResult) {
                Log.e("dandy","success ");
                mModel.insertUser(userEntityBaseResult.getData());
            }
        };
        mModel.mLoginRepository.getUserByLiveData().observe(this, new Observer<UserEntity>() {
            @Override
            public void onChanged(UserEntity entity) {
                if (entity==null){
                    Log.e("dandy","数据库里面没有数据 ");

                }else {
                    Log.e("dandy","数据库中读取出来的 "+entity.toString());

                }
            }
        });

    }

    @OnClick(R.id.login_submit_btn)
    public void onLogin(){
        mModel.login(mPhoneText.getText().toString(), mPwdText.getText().toString());
        mModel.getUserLiveData().observe(this, observer);
    }
}

注:观察者只能初始化一次,不能多次初始化,不然会回调多次的。
当我们对数据库进行操作的时候,会自动响应读取数据库,便于我们及时获取最新的数据
最后我们跑一下试试,看看日志,
在这里插入图片描述

额,What fuck?好吧,Google为了安全真是煞费苦心,
这是API>=27的时候才会出现的,认定我的后台域名不安全,加点东西就可以了,在res目录下新建xml文件夹,然后新增network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

最后在AndroidManifest中application节点下加入
android:networkSecurityConfig="@xml/network_security_config"就OJBK了。
在这里插入图片描述

附上demo地址

RoomLiveDataViewModel结合是为了实现数据持久化、数据观察与界面更新的最佳实践。 Room是Google为Android提供的一个持久化数据库解决方案,它简化了数据库操作并提供了编译时检查,同时支持在后台线程中进行数据库访问。LiveData是一种可观察的数据容器,它可以感知生命周期,并在数据变化时自动更新。ViewModel则负责管理界面相关的数据,并且与Activity或Fragment的生命周期无关,避免了数据丢失或重复请求的问题。 将RoomLiveData结合使用,可以将Room中的数据变化以LiveData的形式暴露出来。这样,在数据库发生变化时,LiveData会自动通知相关观察者进行界面更新。LiveData还具有自动清理机制,当观察者的生命周期结束时会自动停止数据的更新,避免了内存泄漏问题。 而结合ViewModel可以更好地管理界面相关的数据。ViewModel可以在配置改变时(如屏幕旋转)保留数据,并且避免了异步任务导致的内存泄漏。通过在ViewModel中缓存LiveData对象,可以在Activity或Fragment恢复时继续观察数据变化,并保持界面更新的一致性。 总结而言,RoomLiveDataViewModel的结合可以实现一个可靠、高效、响应式的数据处理框架,使得数据的持久化、观察与界面更新变得十分简单。这样的架构设计也有利于代码的维护与扩展,提高了开发效率与用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值