Kotlin+协程+Flow+Retrofit+Jetpack+MVVM简单实现某商城
商城客户端早已泛滥成灾了,但是用来作为学习框架的是最好不过。这次我就来用标题实现
我们就以主界面就说下,如果标题你懂了那么所有功能基本就迎刃而解了。主界面banner,分类和3个列表。这几个无碍乎就是后台拉数据
那么首先放图

一个十分"标准"的界面,底部导航不再用以前那套replace,hide,直接bottomNavigation完事。那么基类是必要的
BaseActivity:
abstract class BaseActivity<VB: ViewBinding>(val block:(LayoutInflater) -> VB) : AppCompatActivity(){
private var _binding: VB? = null
val binding: VB
get() = requireNotNull(_binding) { "biding 已被销毁" }
var mContext: Context? = null
override fun onCreate(savedInstanceState: Bundle?) {
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
super.onCreate(savedInstanceState)
_binding=block(layoutInflater)
setContentView(binding.root)
mContext = this
initView()
requestData()
initData()
allClick()
}
/**
* 初始化布局
* @param savedInstanceState Bundle?
*/
abstract fun initView()
/**
* 初始化数据
*/
abstract fun initData()
/**
* 请求后台返回数据统一在这个方法调用,该页面不需要请求则不用在该方法里写
*/
protected abstract fun requestData()
/**
* 设置所有的点击事件回调监听
* */
abstract fun allClick()
//设置标题,就没必要每个界面找id在赋值
fun setTitleText(title: String?) {
val titileTv = findViewById<TextView>(R.id.title)
titileTv.text = title
}
/**
* 加载中……弹框
*/
fun showLoading() {
WaitDialog.show(this, "")
}
/**
* 关闭提示框
*/
fun dismissLoading() {
WaitDialog.dismiss()
}
//跳转Activity
fun startActivity(clazz: Class<*>?) {
val intent = Intent(this, clazz)
startActivity(intent)
}
//跳转Activity并finish
fun startKillActivity(clazz: Class<*>?) {
val intent = Intent(this, clazz)
startActivity(intent)
finish()
}
//带bundle的跳转界面
fun startActivity(clazz: Class<*>, bundle: Bundle) {
val intent = Intent()
intent.setClass(this, clazz)
intent.putExtras(bundle)
startActivity(intent)
}
fun getResString(resid: Int): String {
return getResources()!!.getString(resid)
}
fun getResColor(resid: Int): Int {
return ContextCompat.getColor(this, resid)
}
/**
* 不少地方有很多RecyclerView所以简要封装下
*
* @param recycleView recycleView控件
* @param layoutManagerType 布局管理器类型 1线性,其他网格布局管理器
*/
fun setRecycleView(recycleView: RecyclerView, layoutManagerType: Int) {
if (layoutManagerType == 1) {
val linearLayoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycleView.setLayoutManager(linearLayoutManager)
} else {
val gridLayoutManager = GridLayoutManager(this, 3)
recycleView.setLayoutManager(gridLayoutManager)
}
}
//标题左边按钮统一设点击事件
fun back(view: View?) {
finish()
}
fun showToast(msg: String) {
Toaster.show(msg)
}
fun showEmptyView(): View? {
return LayoutInflater.from(mContext).inflate(R.layout.empty_view, null)
}
fun startLoginActivity() {
val intent = Intent(mContext, LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
finish()
}
override fun onDestroy() {
super.onDestroy()
// 在 Activity 销毁时,清空 binding 引用,防止内存泄漏
_binding = null
}
此类作用是:
1.findviewbyid已经消失了,取而代之就是Databinding,这个东西不用不知道,一用哎妈呀真香。2.binding就需要自己实例化
3.setTitleText是直接调用标题。具体方法都有注释。我想你应该立马复制进去了是不是。嘛也是。具体用控件的话binding.xxx.setxxx
4.startActivity直接调跳转界面或者带bundle
5.ondestroy把databinding清空
然后是BaseMvvmActivity,这个类才是我们继承的但是记住你觉得这个界面需要网络请求的话就继承否则就是继承BaseActivity。
abstract class BaseMvvmActivity<DB: ViewBinding,VM: ViewModel>(block: (LayoutInflater) -> DB) : BaseActivity<DB>(block) {
lateinit var mViewModel: VM
override fun onCreate(savedInstanceState: Bundle?) {
initViewModel()
super.onCreate(savedInstanceState)
}
fun initViewModel() {
val argument = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
mViewModel = ViewModelProvider(this).get(argument[1] as Class<VM>)
}
这个基类通过泛型参数反射自动创建 ViewModel 实例,更方便使用 ViewModel 实现网络请求(偷偷的说:看别人写的)。
BaseFragment
abstract class BaseFragment <VB: ViewBinding>(val block:(LayoutInflater) -> VB) : Fragment() {
// 内部持有一个可空的 binding 对象,防止在视图销毁后继续引用
private var _binding: VB? = null
var mContext: Activity? = null
/**
* 对外公开的 binding 对象,只有在 onCreateView 到 onDestroyView 之间可用
* 访问时若 _binding 为 null,会抛出 IllegalStateException 提示已被销毁
*/
val binding: VB
get() = requireNotNull(_binding) { "biding 已被销毁" }
override fun onAttach(context: Context) {
super.onAttach(context)
this.mContext = context as Activity
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// 在创建视图时,通过传入的 block 初始化 ViewBinding
_binding = block(layoutInflater)
// 返回根视图给 Fragment 宿主显示
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 视图创建后,调用子类需实现的初始化方法
initView()
requestData()
initData()
allClick()
}
override fun onDestroyView() {
super.onDestroyView()
// 在视图销毁时,清空 binding 引用,防止内存泄漏
_binding = null
}
/**
* 初始化视图组件,例如设置 RecyclerView 的 adapter 或初始化 UI 控件状态
*/
protected abstract fun initView()
/**
* 请求后台返回数据统一在这个方法调用,该页面不需要请求则不用在该方法里写
*/
protected abstract fun requestData()
/**
* 初始化或加载数据,例如发起网络请求或读取本地数据库
*/
protected abstract fun initData()
/**
* 设置所有的点击事件回调监听
*/
protected abstract fun allClick()
}
和baseActivity差不多的。
下面是网络请求部分MVVM,MV表示model和View。为啥可以避免内存泄漏呢?因为有个Observer ,叫做观察者,它作为界面的匿名内部类,它会持有界面的引用,同时 Observer 被 LiveData 持有。另外LiveData还能知晓它绑定的Activity或者Fragment的生命周期,它只会给前台活动的activity回调(这个很厉害).这样你可以放心的在它的回调方法里直接将数据添加到View,而不用担心会不会报错.(你也可以不用费心费力判断Fragment是否还存活)。为了封装网络请求。我们的话调最简单的方法
fun launchUI(errorBlock: (Int?, String?) -> Unit, responseBlock: suspend () -> Unit) {
viewModelScope.launch(Dispatchers.Main) {
safeApiCall(errorBlock = errorBlock, responseBlock)
}
}
我们这里直接用lauch创建一个新的协程,然后切线程后调 safeApiCall方法。那么 safeApiCall干嘛的它是用来在协程作用域中把请求的数据返回出去
/**
* 需要运行在协程作用域中
* @param errorBlock 错误回调
* @param responseBlock 请求函数
*/
suspend fun <T> safeApiCall(
errorBlock: suspend (Int?, String?) -> Unit,
responseBlock: suspend () -> T?
): T? {
try {
return responseBlock()
} catch (e: Exception) {
val exception = ExceptionHandler.handleException(e)
errorBlock(exception.errCode, exception.errMsg)
}
return null
}
ok,简单的网络封装完成下面是VM层。
VM层要通过Repository层,上面有了basemodel,那么少不了BaseRepository
class BaseRepository {
/**
* IO中处理请求
*/
suspend fun <T> requestResponse(requestCall: suspend () -> BaseResponse<T>?): T? {
val response = withContext(Dispatchers.IO) {
requestCall()
} ?: return null
if (response.isFailed()) {
throw ApiException(response.code, response.message)
}
return response.data
}
}
这个方法是请求返回数据,那是怎么请求的呢?
当线程的代码在到达 suspend 函数的时候被掐断,接下来协程会从这个 suspend 函数开始继续往下执行,不过是在指定的线程。指定的?是 suspend 函数指定的,方法内部的 withContext 传入的 Dispatchers.IO 所指定的 IO 线程。
当这个函数执行完毕后,线程又切了回来,「切回来」也就是协程会帮我再 post 一个 Runnable,让我剩下的代码继续回到主线程去执行。执行完成后!线程会自动切回来,也就是说协程会帮我再 post 一个 Runnable,让剩下的代码继续回到主线程去执行。判断返回失败了直接抛出一个异常弹提示也好都行,基类完成那么我们要继承BaseRepository了
class HomeRepository: BaseRepository() {
/**
* 获取主页
*/
suspend fun getHomeGoods(): HomeResponse? {
return requestResponse {
ApiManager.api.home()
}
}
}
继承后定义的挂起方法getHomeGoods冒号后面是返回数据bean,用java都熟悉bean吧。然后return 把requestResponse里面的接口返回出去
@GET("接口地址")
suspend fun home(): BaseResponse<HomeResponse>
然后HomeViewModel
class HomeViewModel: BaseViewModel() {
val homeLiveData = MutableLiveData<HomeResponse?>()
fun getHomeGoods(): LiveData<HomeResponse?>{
launchUI(errorBlock = {code, error ->
}){
val data=homeRepository.getHomeGoods()
homeLiveData.value=data
}
return homeLiveData
}
}
上面MutableLiveData 是 LiveData 的子类,在应用中安全地传递数据更新通知,同时自动管理观察者的生命周期以避免内存泄漏。泛型就是返回数据然后定义一个方法带LiveData去监听数据变化然后调用lauchUi通过homeLiveData映射给界面。最后我们要界面显示出来怎么做
mViewModel.homeLiveData.observe(this){homeResponse ->
//显示相应的数据
}
用vm层去调用livedata的观察者大括号里面就是返回bean对象。所有返回数据都在里面你所要做的是开始疯狂点属性。以上就是全部内容,主页会了其他岂不是手到擒来呢
更多推荐

所有评论(0)