使用Kotlin搭建Android MVVM快速开发框架。
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
从v1.1.0版本开始,minSdkVersion调整为21,分支1.0.x版本minSdkVersion为19;
plugins {
...
id 'kotlin-kapt'
}
android{
...
buildFeatures {
//启用databinding
dataBinding = true
}
}
dependencies {
implementation 'com.github.shenbengit:MVVMKit:Tag'
}
如果您想快速使用,您需要先了解Kotlin语法、Databinding、Koin等相关知识。
- Lifecycle:生命周期感知型组件可执行操作来响应另一个组件(如 Activity 和 Fragment)的生命周期状态的变化;
- LiveData:LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期;
- ViewModel:ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据;
- okhttp:OkHttp is an HTTP client that’s efficient;
- Retrofit:A type-safe HTTP client for Android and Java;
- koin:一个实用的轻量级Kotlin依赖注入框架;
- Moshi:Moshi是一个适用于Android、Java和Kotlin的JSON 库;
- coil:由Kotlin Coroutines支持的Android图像加载库;
- coroutines:Kotlin coroutines;
- MMKV:MMKV是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强,代替SharedPreferences;
- SFragmentation:框架负责管理fragment的各种操作,相比于google新出的navigation框架,更加灵活多变,易于使用;
- XLog:轻量、美观强大、可扩展的 Android 和 Java 日志库;
- Toasty:吐司;
- LoadingDialog:Android LoadingDialog;
建议在Application中执行.
class App : Application() {
override fun onCreate() {
super.onCreate()
val koinApplication =
KoinAndroidApplication
.create(
this,
if (BuildConfig.DEBUG) Level.ERROR else Level.ERROR
)
.modules(appModule)//参考[appModule](https://github.com/shenbengit/SrsRtcAndroidClient/blob/132bc94d4a2c6a53f7af96784eaf75877477cd8b/app/src/main/java/com/shencoder/srs_rtc_android_client/di/AppModule.kt#L20)
//初始化Logger
// initLogger(Constant.TAG)
//初始化吐司
// initToasty()
//初始化MMKV
// initMMKV(if (BuildConfig.DEBUG) MMKVLogLevel.LevelDebug else MMKVLogLevel.LevelNone)
//初始化Fragmentation
// initFragmentation(BuildConfig.DEBUG)
//初始化Koin
// initKoin(koinApplication)
//快速初始化,包括上面的五个方法,只不过是默认配置
globalInit(BuildConfig.DEBUG, Constant.TAG, koinApplication)
}
}
一般来说我们自己的项目都会有自己的BaseActivity用于封装基础逻辑,所以需要我们的基类要继承BaseSupportActivity,自定义相关方法。
abstract class BaseActivity<VM : BaseViewModel<out IRepository>, VDB : ViewDataBinding> :
BaseSupportActivity<VM, VDB>(), CustomAdapt {
override fun isBaseOnWidth(): Boolean {
return true
}
override fun getSizeInDp(): Float {
return Constant.DEFAULT_SIZE_IN_DP
}
override fun initLoadingDialog(): Dialog {
return LoadingDialog.builder(this)
.setHintText(getString(R.string.loading))
.create()
}
}
使用示例
class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>() {
override fun getLayoutId(): Int {
return R.layout.activity_main
}
override fun injectViewModel(): Lazy<MainViewModel> {
return viewModel()
}
override fun getViewModelId(): Int {
return BR.viewModel
}
override fun initView() {
}
override fun initData(savedInstanceState: Bundle?) {
}
}
同BaseSupportActivity.
abstract class BaseFragment<VM : BaseViewModel<out IRepository>, VDB : ViewDataBinding> :
BaseSupportFragment<VM, VDB>(), CustomAdapt {
override fun isBaseOnWidth(): Boolean {
return true
}
override fun getSizeInDp(): Float {
return Constant.DEFAULT_SIZE_IN_DP
}
override fun initLoadingDialog(): Dialog {
return LoadingDialog.builder(requireContext())
.setHintText(getString(R.string.loading))
.create()
}
}
使用示例
class TestFragment : BaseFragment<DefaultViewModel, FragmentTestBinding>() {
override fun getLayoutId(): Int {
return R.layout.fragment_test
}
/**
* 注入ViewModel
* @see [viewModel]
* @see [sharedViewModel]
* @see [sharedStateViewModel]
*/
override fun injectViewModel(): Lazy<DefaultViewModel> {
return viewModel()
}
override fun getViewModelId(): Int {
return BR.viewModel
}
override fun initView() {
}
override fun initData(savedInstanceState: Bundle?) {
}
}
ViewMode的基类;
class MainViewModel(
application: Application,
repo: BaseNothingRepository
) : BaseViewModel<BaseNothingRepository>(application, repo) {
override fun onCreate(owner: LifecycleOwner) {
...
}
override fun onDestroy(owner: LifecycleOwner) {
...
}
}
如果你的ViewModel中不需要写相关逻辑,比较简单,则可以用DefaultViewModel进行占位。
数据仓库基类,在创建BaseViewModel需要使用,自行创建并继承相关类即可;
/**
* 网络请求和本地数据库均需要使用的情况
*/
open class BaseBothRepository<T : IRemoteDataSource, R : ILocalDataSource>(
protected val remoteDataSource: T,
protected val localDataSource: R
) : BaseRepository()
/**
* 仅使用本地数据库
*/
open class BaseLocalRepository<T : ILocalDataSource>(
protected val remoteDataSource: T
) : BaseRepository()
/**
* 仅使用网络请求
*/
open class BaseRemoteRepository<T : IRemoteDataSource>(
protected val remoteDataSource: T
) : BaseRepository()
/**
* 不需要数据
*/
class BaseNothingRepository : BaseRepository()
如果你没有数据请求相关操作,则可以用BaseNothingRepository进行占位。
自定义RetrofitClient,示例:
class RetrofitClient : BaseRetrofitClient() {
companion object {
private const val DEFAULT_MILLISECONDS: Long = 30
}
private lateinit var apiService: ApiService
override fun generateOkHttpBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
val interceptor = HttpLoggingInterceptor { message -> XLog.i(message) }
interceptor.level =
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC
else HttpLoggingInterceptor.Level.NONE
return builder.readTimeout(DEFAULT_MILLISECONDS, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_MILLISECONDS, TimeUnit.SECONDS)
.connectTimeout(DEFAULT_MILLISECONDS, TimeUnit.SECONDS)
.addInterceptor(interceptor)
}
override fun generateRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
return builder
}
/**
* 动态修改Retrofit-baseUrl
*/
fun setBaseUrl(baseUrl: String) {
apiService = getApiService(ApiService::class.java, baseUrl)
}
fun getApiService(): ApiService {
if (this::apiService.isInitialized.not()) {
setBaseUrl(Constants.BASE_API_HTTPS_URL)
}
return apiService
}
}
使用示例:
GlobalScope.launch {
//下载文件
DownloadFile(
"http://ip:port/xxx.txt",
"test.txt"
).progress { totalSize: Long, downloadSize: Long, /*[0-1]*/progress: Float ->
//进度回调
}.success { file: File ->
//下载成功
}.error { error: Throwable ->
//下载失败
}.startDownload()//开始下载 suspend 方法
}
这个是在BaseViewModel中快速使用网络请求所用,不强制使用。
一般来说网络请求结果返回都会有对应的状态返回,如成功、失败、错误等。
这样在返回结果实体bean中去继承此类,则可以快速执行条件判断,如下:
open class ApiResponse<T>(
@Json(name = "code")
val code: Int,
@Json(name = "data")
val data: T?,
@Json(name = "msg")
val msg: String
) : BaseResponse<T>() {
override fun isSuccess(): Boolean {
return code == 200
}
override fun getResponseCode(): Int {
return code
}
override fun getResponseMsg(): String {
return msg
}
override fun getResponseData(): T? {
return data
}
}
data class UserInfoBean(
@Json(name = "createdAt")
val createdAt: String,
@Json(name = "id")
val id: Int,
@Json(name = "userId")
val userId: String,
@Json(name = "userType")
val userType: String,
@Json(name = "username")
val username: String
)
interface ApiService {
/**
* 获取所有用户信息
* 仅查询客户端;
* query clients only.
*/
@GET("/srs_rtc/user/getAllUserInfo")
suspend fun getAllUser(): ApiResponse<List<UserInfoBean>>
}
class MainViewModel(
application: Application,
repo: BaseNothingRepository
) : BaseViewModel<BaseNothingRepository>(application, repo) {
private val retrofitClient : RetrofitClient by inject()
override fun onCreate(owner: LifecycleOwner) {
httpRequest({
retrofitClient.getApiService().getAllUser()
}, onSuccess = {
//请求成功
val data = it.data
if (data == null) {//之所以判断是否为null,是因为考虑到有的网络请求返回值是只有状态值,而没有内容。
toastWarning("userList is null.")
return@httpRequest
}
analyticalData(data, unSelectedSet)
}, onFailure = {
//请求失败
XLog.w("getAllUser failed,code:${it.code}, msg: ${it.msg}")
toastWarning("getAllUser failed: ${it.msg}")
}, onError = {
//网络请求错误
XLog.w("getAllUser error: ${it.throwable.message}")
toastWarning("getAllUser error: ${it.throwable.message}")
}, isShowLoadingDialog = true)
}
}
这一部分需要你先去了解Koin相关知识(比Dagger简单多了)
本库中在初始化时已经默认加入了一些要默认注入的库:
private val defaultModule = module {
factory { BaseNothingRepository() }
viewModel { DefaultViewModel(get(), get()) }
}
/**
* 单例模式
*/
private val singleModule = module {
//默认加入了MMKV
single { MMKV.defaultMMKV() }
single { DownloadRetrofitClient() }
}
internal val appModule = listOf(defaultModule, singleModule)
比如说上面的RetrofitClient,我们就可以这样:
private val singleModule = module {
single { RetrofitClient() }
}
val appModule = mutableListOf(singleModule)
用的时候直接这样:
private val retrofitClient : RetrofitClient by inject()
其他更多示例,请看SrsRtcAndroidClient、AppModule、ViewModelModule,代码是最好的老师。
本库中封装了一些Databinding中常用的一些方法,具体可以看下DataBindingAdapter,方便快速开发。
这一步对Toast进行了扩展,在Activity、Fragment、Dialog、ViewModel中可以直接使用,如:
toastError("error")
toastSuccess("success")
toastWarning("warning")
toastNormal("normal")
BaseViewModel相关扩展方法在BaseViewModelExt.
还有一些功能方法封装,如MoshiUtil、Nv21ToBitmap等,具体在util包下,可自行查看。