Retrofit+Jetpack(LiveData,Lifecycles,ViewModel,Room)+koin构建Android 官方的推荐架构指南MVVM架构

在这里插入图片描述

基本架构:Android 官方的推荐架构指南https://developer.android.google.cn/jetpack/guide#recommended-app-arch

在这里插入图片描述
请注意,每个组件仅依赖于其下一级的组件。
例如,Activity 和 Fragment 仅依赖于视图模型。存储区是唯一依赖于其他多个类的类;在本例中,存储区依赖于持久性数据模型和远程后端数据源。

这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的用户信息。如果此数据已过时,则应用的存储区模块将开始在后台更新数据

  • 获取本地数据:

Activity持有ViewModel,
ViewModel持有Repository,
Repository持有Dao,
Dao定义具体的增删改查方法

  • 获取网络数据:

Activity持有ViewModel,
ViewModel持有Repository,
Repository持有Service,
Service定义具体的get,post请求方法

下面是参考官方的sunflower项目的案例

  • 获取本地数据库数据

先来俩按钮:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/tv_add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加本地数据" />
    <Button
        android:id="@+id/tv_getList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取电影列表" />


</LinearLayout>

然后设置各自的点击事件
既然是参照sunflower,我们先看本地数据操作的流程:
简单来说:Activity持有ViewModel,ViewModel持有Repository,Repository持有Dao,Dao定义具体的增删改查方法

1.初始化数据库和定义Dao
//entities是个数组,可以存放多张表
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDataBase : RoomDatabase() {
//把你的Dao都在这里定义为抽象方法即可
    abstract fun getUserDao(): UserDao

    companion object {
        const val DATABASE_NAME = "zx-db"

        @Volatile
        private var instance: AppDataBase? = null

        fun getInstance(context: Context): AppDataBase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

        private fun buildDatabase(context: Context): AppDataBase {
            return Room.databaseBuilder(context, AppDataBase::class.java, DATABASE_NAME).build()
        }
    }
}
@Entity
data class User (
    @PrimaryKey
    val id: Int,
    val name:String,
    val age: Int
)
@Dao
interface UserDao {
    @Query("select * from User ORDER BY id")
    fun getUsers(): LiveData<List<User>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUsers(users: List<User>)
}
2.创建Repository
class UserRepository constructor(private val userDao: UserDao) {


    fun getUser() = userDao.getUsers()
    suspend fun insertUser(users: List<User>) = userDao.insertUsers(users)

    companion object {
        @Volatile
        private var instance: UserRepository? = null

        fun getInstance(userDao: UserDao) =
            instance ?: synchronized(this) {
                instance
                    ?: UserRepository(userDao)
                        .also { instance = it }
            }
    }
}
3.创建ViewModel(使用ViewModelProvider.Factory 创建)
class UserListViewModel constructor(val userRepository: UserRepository) : ViewModel() {

    val users: LiveData<List<User>> = userRepository.getUser()

    suspend fun insertUser(users: List<User>) = userRepository.insertUser(users)



}
class UserListViewModelFactory constructor(val repository: UserRepository) :
    ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return UserListViewModel(repository) as T
    }
}
4.最后在MainActivity中得到ViewModel并调用相应方法
class MainActivity : AppCompatActivity() {
//初始化viewmodel
private val userViewModel: UserListViewModel by viewModels {
          val appDataBase = AppDataBase.getInstance(this)
          val repository = UserRepository.getInstance(appDataBase.getUserDao())
          UserListViewModelFactory(repository)
      }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

      //观察users
        userViewModel.users.observe(this, Observer {
            for (a in it) {
                println("${a.name},${a.age},${a.id}")
            }
        })

      tv_add.setOnClickListener {

            val user1: User = User(1, "张飞", 22)
            val user2: User = User(2, "诸葛亮", 23)
            val user3: User = User(3, "张对对对", 27)
            val user4: User = User(4, "张对对对22", 27)

            val users = listOf<User>(user1, user2, user3, user4)

            lifecycleScope.launch {
                userViewModel.insertUser(users)
            }

        }
 }
}
  • 获取网络数据

这样操作本地数据的方法就完成了,下面我们看看使用Retrofit网络请求的处理,也是一样
Activity持有ViewModel,ViewModel持有Repository,Repository持有Service,Service定义具体的get,post请求方法

1.先定义好一个Service
interface MsgService {
    @GET("videoHomeTab")
    suspend fun getVideoTab(): HttpResponse<List<Video>>
}

创建一个kt文件,所有的data都写在这一个里面

data class Video(
    val nameType: Int,
    val apiUrl: String,
    val name: String,
    val tabType: Int,
    val id: Int
)
data class HttpResponse<out T>(val code: Int, val msg: String, val result: T)
2.创建Repository
class MsgRepository constructor(
    private val msgService: MsgService
) {
    suspend fun getVideoTab(): HttpResult<List<Video>> {
        return apiCall { msgService.getVideoTab() }
    }

    companion object {
        @Volatile
        private var instance: MsgRepository? = null

        fun getInstance(msgService: MsgService) =
            instance ?: synchronized(this) {
                instance
                    ?: MsgRepository(msgService)
                        .also { instance = it }
            }
    }

}
sealed class HttpResult<out T> {

    data class Success<out T>(val data: T) : HttpResult<T>()

    data class Error(val e: String) : HttpResult<Nothing>()


    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$e]"
        }
    }

    fun data(): T {
        return (this as Success<T>).data
    }

    fun isSucceed(): Boolean {
        return this is Success
    }

    fun error(): String {
        return (this as Error).e
    }

    companion object {

        fun handleException(e: Exception): String {
            val context: Context = IApplication.CONTEXT

            val result = when (e) {
                null -> context.getString(R.string.basic_error_unknown)
                is CertificateException, is SSLHandshakeException
                -> context.getString(R.string.basic_error_certificate)
                is MalformedURLException -> context.getString(R.string.basic_error_service_domain)
                is HttpException -> context.getString(R.string.basic_error_service)
                is InterruptedIOException, is SocketException, is TimeoutException, is UnknownHostException
                -> context.getString(R.string.basic_error_network)
                is JsonSyntaxException -> context.getString(R.string.basic_error_response_parse)
                is IOException -> context.getString(R.string.basic_error_request)
                is ClassCastException -> context.getString(R.string.basic_error_data_structure)
                else -> e.toString()

            }
            return result

        }
    }


}

创建一个kt文件

suspend fun <T> apiCall(call: suspend () -> HttpResponse<T>): HttpResult<T> {
    return try {
        call().let {
            if (it.code == 0 || it.code == 200) {
                HttpResult.Success(it.result)
            } else {
                HttpResult.Error(it.msg)
            }
        }
    } catch (e: Exception) {
        HttpResult.Error(HttpResult.handleException(e))
    }


}
3.创建ViewModel(使用ViewModelProvider.Factory 创建)
class MsgViewModel constructor(val repository: MsgRepository) : ViewModel() {

    private val _getVideoTab = MutableLiveData<HttpResult<List<Video>>>()
    val getVideoTab: LiveData<HttpResult<List<Video>>>
        get() = _getVideoTab


    fun getVideoTab() {
        viewModelScope.launch {
            _getVideoTab.value = repository.getVideoTab()
        }
    }

}
class MsgViewModelFactory constructor(val repository: MsgRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MsgViewModel(repository) as T
    }
}
4.最后在MainActivity中得到ViewModel并调用相应方法
class MainActivity : AppCompatActivity() {

     private val msgViewModel: MsgViewModel by viewModels {
       val retrofit = retrofitClient.createRetrofit("https://api.apiopen.top")
       val msgService = retrofit.create(MsgService::class.java)
       val repository = MsgRepository.getInstance(msgService)
       MsgViewModelFactory(repository)
   }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        msgViewModel.getVideoTab.observe(this, Observer {
            if (it.isSucceed()) {
                for (a in it.data()) {
                    println("${a.apiUrl},${a.name}")
                }

            } else {
                Toast.makeText(this, it.error(), Toast.LENGTH_LONG).show()
            }
        })
}

}

retrofit的配置:

class RetrofitClient {


    fun createRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(provideClient(OkHttpClient.Builder()))
            .addConverterFactory(GsonConverterFactory.create())
            .build()

    }

    private fun provideClient(builder: OkHttpClient.Builder): OkHttpClient {
        if (BuildConfig.DEBUG) {
            builder.addNetworkInterceptor(initLogInterceptor())
        }
        builder.addInterceptor(initHeader())
        builder.connectTimeout(30, TimeUnit.SECONDS)
        builder.readTimeout(30, TimeUnit.SECONDS)
        builder.writeTimeout(30, TimeUnit.SECONDS)
        return builder.build()
    }

    //----------------------------------------具体配置---------------------------------------------

    private fun initHeader(): Interceptor {
        return Interceptor { chain ->
            val originalRequest = chain.request()
            val requestBuilder = originalRequest.newBuilder()
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .method(originalRequest.method, originalRequest.body)
            val request = requestBuilder.build()
            chain.proceed(request)
        }
    }

    private fun initLogInterceptor(): HttpLoggingInterceptor {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        return loggingInterceptor
    }
}
class IApplication : Application() {
    companion object {
        var CONTEXT: Context by Delegates.notNull()
    }

    override fun onCreate() {
        super.onCreate()
        CONTEXT = applicationContext
    }
}

上述基本就实现了获取本地和网络数据

但是viewmodle的初始化不是很完美,我这边是koin来简化一下,并且把dao和service合并到一个Repository里面来
改动的地方:

1.MsgRepository持有dao和service
class MsgRepository constructor(
    private val msgService: MsgService, private val userDao: UserDao
) {

    suspend fun getVideoTab(): HttpResult<List<Video>> {
        return apiCall { msgService.getVideoTab() }
    }

    fun getUser() = userDao.getUsers()
    suspend fun insertUser(users: List<User>) = userDao.insertUsers(users)

    companion object {
        @Volatile
        private var instance: MsgRepository? = null

        fun getInstance(msgService: MsgService, userDao: UserDao) =
            instance ?: synchronized(this) {
                instance
                    ?: MsgRepository(msgService, userDao)
                        .also { instance = it }
            }
    }

}

这里就可以就同一个Repository直接调用了

class MsgViewModel constructor(val repository: MsgRepository) : ViewModel() {

    private val _getVideoTab = MutableLiveData<HttpResult<List<Video>>>()
    val getVideoTab: LiveData<HttpResult<List<Video>>>
        get() = _getVideoTab

    fun getVideoTab() {
        viewModelScope.launch {
            _getVideoTab.value = repository.getVideoTab()
        }
    }

    val users: LiveData<List<User>> = repository.getUser()

    suspend fun insertUser(users: List<User>) = repository.insertUser(users)
}
2.创建AppModule.kt文件
val viewModelModule = module {
    factory { MsgViewModel(get()) }
}
val repositoryModule = module {
    single { MsgRepository(get(), get()) }
}
val dataModule = module {
    single {
        RetrofitClient().createRetrofit("https://api.apiopen.top").create(MsgService::class.java)
    }
    single {
        AppDataBase.getInstance(IApplication.CONTEXT).getUserDao()
    }
}

val appModule = listOf(viewModelModule, repositoryModule, dataModule)
3.Application中初始化
class IApplication : Application() {
    companion object {
        var CONTEXT: Context by Delegates.notNull()
    }

    override fun onCreate() {
        super.onCreate()
        CONTEXT = applicationContext
    //初始化koin
        startKoin {
            androidContext(this@IApplication)
            modules(appModule)

        }
    }
}
4.最后Mainactiviy就可以这样调用了
class MainActivity : AppCompatActivity() {
    //通过koin获取ViewModel
    val msgViewModel: MsgViewModel by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        observers()


        tv_add.setOnClickListener {

            val user1: User = User(1, "张飞", 22)
            val user2: User = User(2, "诸葛亮", 23)
            val user3: User = User(3, "张对对对", 27)
            val user4: User = User(4, "张对对对22", 27)

            val users = listOf<User>(user1, user2, user3, user4)

            lifecycleScope.launch {
                msgViewModel.insertUser(users)
            }

        }

        tv_getList.setOnClickListener {
            msgViewModel.getVideoTab()
        }
    }

    private fun observers() {
    //通过msgViewModel获取网络数据
        msgViewModel.getVideoTab.observe(this, Observer {
            if (it.isSucceed()) {
                for (a in it.data()) {
                    println("${a.apiUrl},${a.name}")
                }

            } else {
                Toast.makeText(this, it.error(), Toast.LENGTH_LONG).show()
            }
        })

//通过msgViewModel获取本地数据
        msgViewModel.users.observe(this, Observer {
            for (a in it) {
                println("${a.name},${a.age},${a.id}")
            }
        })

    }
}

本案例涉及到的技术框架:LiveData,Lifecycles,ViewModel,Room,Koin,Retrofit
官方文档:https://developer.android.google.cn/jetpack

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哆啦A梦z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值