需求:注册后首页弹出 toast
问题总结
问题1:OPPO手机默认关闭 设置===通知与状态栏===通知管理===APP名称打开通知
问题2:怎么加入接口不熟悉 activity/fragment
问题3:在不熟悉逻辑的情况下,在什么地方加入逻辑
问题4:Kotlin 、dagge 不够熟悉
问题5:前期准备不充分,浪费了很多时间,不明确时间节点
尝试过的错误方法
错误方法1:HomeFragment 处理逻辑;问题:如果从界面Fragment到注册界面回来,没弹出
错误方法2:登录过后,方法拦截; 问题:如果输入账号,验证码后就会弹出Toast
正确的方法(◔ ‸◔? (⊙.⊙)):
依次往下调用
SetPasswordActivity
SetPasswordViewModel
SetPasswordPresenter
AccountDataService---AccountDataManagerImpl
对比代码(无用)
data class ToastModel(
val content: String?,
val type: String?
)
结构
SetPasswordActivity.kt
@Route(path = ARouters.Account.SET_PASSWORD)
class SetPasswordActivity : InjectedActivity(R.layout.activity_set_password) {
@Inject
lateinit var viewModel: SetPasswordViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initView()
}
private fun initView() {
// 提交按钮
btn_submit.clickThrottle {
viewModel.setPassword(ket_password.text.toString(), ket_invite_code?.text?.toString())
}
}
}
@Keep
data class ToastEntity(
@SerializedName("content")
var content: String? = null,
@SerializedName("type")
var type: String? = null
):Parcelable{
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(content)
parcel.writeString(type)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ToastEntity> {
override fun createFromParcel(parcel: Parcel): ToastEntity {
return ToastEntity(parcel)
}
override fun newArray(size: Int): Array<ToastEntity?> {
return arrayOfNulls(size)
}
}
}
SetPasswordViewModel.kt
fun setPassword(password: String, inviteCode: String?) = launch {
。。。
.onSuccess {
accountDataService?.getToastContent()
}
。。。
}
AuthService.kt
interface AuthService {
/**
* 获取toast 列表
*/
@GET("/message/toast/list/")
suspend fun getTokenStatus(): Response.List<ToastEntity>
}
AccountDataManagerImpl.kt
@Suppress("SENSELESS_COMPARISON", "USELESS_IS_CHECK")
@Route(path = ARouters.Account.ACCOUNT_DATA_SERVICE, name = "账户数据服务")
class AccountDataManagerImpl : AccountDataManager, AccountDataService,
BaseDataServiceImpl<AuthService>() {
override fun getToastContent() {
safetyLaunch {
delay(2000)
safeApiCall {
service.getTokenStatus()
}.onSuccess {
if (it?.list.isNullOrEmpty()) {
return@onSuccess
}
info(it?.list?.get(0)?.content)
}.onFailure {
it.handle()
}
}
}
}
AccountDataService.kt.api
interface AccountDataService : IProvider {
/**
* 获取toast 列表
*/
fun getToastContent()
}
关键代码详细解释
Coroutine 是轻量级的线程
使用 Coroutine 必须要先创建一个对应的 CoroutineScope
一般而言,在应用中具有生命周期的组件应该实现 CoroutineScope 接口,并负责该组件内 Coroutine 的创建和管理
为了方便创建 Coroutine,在 CoroutineScope 上有很多扩展函数,比如 launch、async、actor、cancel 等
GlobalScope 是 CoroutineScope 的一个单例实现
Job 代表 launch 创建的一个 Coroutine 实例即可,通过这个 job 对象可以控制这个 Coroutine 实例,比如调用 cancel 函数可以取消执行
suspend
修饰符,它可以告诉编译器,该函数需要在协程中执行
/**
* 安全启动.
* @param block suspend CoroutineScope.() -> Unit
* @return JobWrapper
*/
fun safetyLaunch(block: suspend CoroutineScope.() -> Unit): JobWrapper {
val jobWrapper = JobWrapper()
jobWrapper.job = GlobalScope.launch(CoroutinesDispatchers.ui + rootJob) {
runCatching {
block.invoke(this)
}.onFailure {
jobWrapper.onFailure?.invoke(it) ?: Logger.e(it, "safetyLaunch")
}
}
return jobWrapper
}
延迟2s
delay(2000)
异常捕获
/**
* 封装api请求,并进行异常捕捉处理.
* @param call suspend () -> Response<T>
* @return Result<T>
*/
suspend fun <T : Any> safeApiCall(call: suspend () -> Response<T>): Result<T> =
try {
Result.Success(call.invoke().unwrap())
} catch (e: Throwable) {
Result.Error(e)
}
list为空判断
if (it?.list.isNullOrEmpty()) {
return@onSuccess
}
info(it?.list?.get(0)?.content)
}