我們先參考一下官方的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 {
single { 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.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.users.observe(this, Observer {
for (a in it) {
println("${a.name},${a.age},${a.id}")
}
})
}
}