Retrofit+Jetpack+koin構建MVVM架構

我們先參考一下官方的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}")
            }
        })

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章