Commit 0c1fe3e6 by TaylorZhang

feat(项目):完成mvvm-demo最小Demo

parent ffc17dde
......@@ -3,6 +3,7 @@
package="com.autocareai.mvvmdemo">
<application
android:name=".demo.Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
......
package com.autocareai.mvvmdemo.common.api
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 通用网络请求接口
* version: 1.0.0
* </pre>
*/
object CommonApi {
/**
* api地址
*/
internal val API_URL = "https://wanandroid.com/"
}
\ No newline at end of file
package com.autocareai.mvvmdemo.common.http
import androidx.lifecycle.LifecycleOwner
import com.autocareai.lib.net.converter.IConverter
import com.autocareai.lib.net.exception.BusinessErrorException
import com.autocareai.lib.util.JsonUtil
import okhttp3.ResponseBody
import org.json.JSONObject
import retrofit2.Response
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 将Http响应数据转换成一个对象,去除最外层封装
* version: 1.0.0
* </pre>
*/
class ResponseConverter<D>(private val clazz: Class<D>) : IConverter<D> {
override fun convertResponse(owner: LifecycleOwner?, response: Response<ResponseBody>): D? {
val json = response.body()?.string() ?: return null
val jsonObj = JSONObject(json)
val code = jsonObj.getInt("errorCode")
if (code == 0) {
return JsonUtil.parseObject(jsonObj.getJSONObject("data").toString(), clazz)
} else {
throw BusinessErrorException(code, jsonObj.getString("errorMsg"))
}
}
}
\ No newline at end of file
package com.autocareai.mvvmdemo.common.http
import com.autocareai.lib.net.observable.HttpObservable
import com.autocareai.lib.net.observable.HttpObservableImpl
import com.autocareai.lib.net.request.BaseRequest
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 网络请求基类扩展
* version: 1.0.0
* </pre>
*/
/**
* 将Http响应数据转换成一个对象,去除最外层封装
*/
inline fun <reified D : Any> BaseRequest<*>.asResponse(): HttpObservable<D> {
return HttpObservableImpl(this, ResponseConverter(D::class.java))
}
\ No newline at end of file
package com.autocareai.mvvmdemo.common.tool
import com.autocareai.lib.net.HttpConfig
import com.autocareai.lib.net.HttpUtil
import com.autocareai.mvvmdemo.common.api.CommonApi
import okhttp3.logging.HttpLoggingInterceptor
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 网络请求工具类
* version: 1.0.0
* </pre>
*/
object HttpTool {
// 网络请求连接超时时间
private const val DEFAULT_CONNECT_TIMEOUT = 5000L
// 网络请求其他超时时间
private const val OTHER_TIME_OUT = 30000L
/**
* 初始化网络请求框架
*/
fun init() {
val httpConfig = HttpConfig.Builder()
.baseUrl(CommonApi.API_URL)
// 设置全局超时时间
.connectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT)
.readTimeoutMillis(OTHER_TIME_OUT)
.writeTimeoutMillis(OTHER_TIME_OUT)
.setLogLevel(HttpLoggingInterceptor.Level.BODY)
.build()
HttpUtil.initHttpConfig(httpConfig)
// 初始化全局错误监听
initGlobalErrorListener()
}
private fun initGlobalErrorListener() {
HttpUtil.setGlobalErrorListener { observable, code, message ->
// 处理全局错误
return@setGlobalErrorListener false
}
}
}
\ No newline at end of file
package com.autocareai.mvvmdemo.common.view
import androidx.lifecycle.Observer
import com.autocareai.lib.lifecycle.view.LibBaseLifecycleActivity
import com.autocareai.lib.util.ToastUtil
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 生命周期Activity基类
* version: 1.0.0
* </pre>
*/
abstract class BaseLifecycleActivity<VM : BaseViewModel> : LibBaseLifecycleActivity<VM>() {
override fun initLifecycleObserver() {
super.initLifecycleObserver()
mViewModel.shortToastEvent.observe(this, Observer {
// 显示toast
ToastUtil.shortToast(it)
})
}
}
\ No newline at end of file
package com.autocareai.mvvmdemo.common.view
import android.widget.Toast
import androidx.annotation.StringRes
import com.autocareai.lib.lifecycle.extension.post
import com.autocareai.lib.lifecycle.livedata.SingleLiveEvent
import com.autocareai.lib.lifecycle.viewmodel.LibBaseViewModel
import com.autocareai.lib.util.ResourcesUtil
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 生命周期ViewModel基类
* version: 1.0.0
* </pre>
*/
open class BaseViewModel : LibBaseViewModel() {
/**
* 显示toast,时长为[Toast.LENGTH_SHORT]
*/
val shortToastEvent = SingleLiveEvent<CharSequence>()
/**
* 显示toast,时长为[Toast.LENGTH_SHORT]
*
* @param msg toast文本
*/
protected fun shortToast(msg: CharSequence) {
shortToastEvent.post(msg)
}
/**
* 显示toast,时长为[Toast.LENGTH_SHORT]
*
* @param id toast文本资源id
*/
protected fun shortToast(@StringRes id: Int) {
shortToastEvent.post(ResourcesUtil.getString(id))
}
}
\ No newline at end of file
package com.autocareai.mvvmdemo.demo
import com.autocareai.lib.application.LibApplication
import com.autocareai.lib.util.LogUtil
import com.autocareai.mvvmdemo.common.tool.HttpTool
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : Application
* version: 1.0.0
* </pre>
*/
class Application : LibApplication() {
override fun isRelease(): Boolean {
// 初始化日志框架
LogUtil.init()
HttpTool.init()
return false
}
}
\ No newline at end of file
package com.autocareai.mvvmdemo.demo.api
import com.autocareai.lib.net.HttpUtil
import com.autocareai.lib.net.observable.HttpObservable
import com.autocareai.mvvmdemo.common.http.asResponse
import com.autocareai.mvvmdemo.demo.enity.UserEntity
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : Demo网络请求接口
* version: 1.0.0
* </pre>
*/
object DemoApi {
// 登录
private const val LOGIN = "user/login"
/**
* 登录
*
* @param phone 手机号
* @param password 密码
*/
fun login(phone: String, password: String): HttpObservable<UserEntity> {
return HttpUtil.postForm(LOGIN)
.param("username", phone)
.param("password", password)
.asResponse()
}
}
\ No newline at end of file
package com.autocareai.mvvmdemo.demo.enity
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 用户数据
* version: 1.0.0
* </pre>
*/
@Parcelize
class UserEntity : Parcelable {
// TODO: 2020/6/24 这里不用使用 data class,普通的 class 即可
// TODO: 2020/6/24 每一个字段都需要写注释,对于多状态字段,或者布尔字段,要写明各种情况下的含义
// TODO: 2020/6/24 字段必须赋初值,使用简写即可,比如 var email = "",而不是 var email = ""
// xx
var admin = false
// xx
var chapterTops = ArrayList<String>()
// xx
var collectIds = ArrayList<String>()
// xx
var email = ""
// xx
var icon = ""
// xx
var id = ""
// xx
var nickname = ""
// xx
var password = ""
// xx
var token = ""
// xx
var type = 0
// xx
var username = ""
}
\ No newline at end of file
package com.autocareai.mvvmdemo.demo.main
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.Observer
import com.autocareai.lib.extension.onClick
import com.autocareai.mvvmdemo.R
import com.autocareai.mvvmdemo.common.view.BaseLifecycleActivity
import kotlinx.android.synthetic.main.activity_main.*
/**
* <pre>
......@@ -12,10 +14,28 @@ import com.autocareai.mvvmdemo.R
* version: 1.0.0
* </pre>
*/
class MainActivity : AppCompatActivity() {
class MainActivity : BaseLifecycleActivity<MainViewModel>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
override fun getLayoutId(): Int = R.layout.activity_main
override fun getViewModelClass(): Class<MainViewModel> = MainViewModel::class.java
override fun initListener() {
super.initListener()
// TODO: 2020/6/24 如果防止用户短时间多次点击,使用onClick,如果用户可以多次点击,还是使用原生的setOnClickListener
btnLogin.onClick {
// 登录
mViewModel.login(this, etPhone.text.toString(), etPwd.text.toString())
}
}
override fun initLifecycleObserver() {
super.initLifecycleObserver()
mViewModel.toListEvent.observe(this, Observer {
// 跳转至列表界面
// TODO: 2020/6/24 跳转至列表界面
})
}
}
package com.autocareai.mvvmdemo.demo.main
import androidx.lifecycle.LifecycleOwner
import com.autocareai.lib.lifecycle.extension.post
import com.autocareai.lib.lifecycle.livedata.SingleLiveEvent
import com.autocareai.mvvmdemo.R
import com.autocareai.mvvmdemo.common.view.BaseViewModel
import com.autocareai.mvvmdemo.demo.api.DemoApi
import com.autocareai.mvvmdemo.demo.enity.UserEntity
/**
* <pre>
* author : Taylor Zhang
* time : 2020/06/24
* desc : 主界面ViewModel
* version: 1.0.0
* </pre>
*/
class MainViewModel : BaseViewModel() {
/**
* 跳转至列表界面事件
*/
val toListEvent = SingleLiveEvent<UserEntity>()
/**
* 登录
*/
fun login(owner: LifecycleOwner, phone: String, password: String) {
checkInput(owner, phone, password)
}
private fun checkInput(owner: LifecycleOwner, phone: String, password: String) {
// TODO: 2020/6/24 检查用户输入是否合规的逻辑也应该放在ViewModel中
if (phone.isEmpty()) {
shortToast(R.string.main_please_input_phone)
return
}
if (password.isEmpty()) {
shortToast(R.string.main_please_input_password)
return
}
realLogin(owner, phone, password)
}
private fun realLogin(owner: LifecycleOwner, phone: String, password: String) {
DemoApi.login(phone, password)
.attachLifecycle(owner)
.onSuccess {
shortToast(R.string.main_login_success)
toListEvent.post(it)
}
.onError { _, message ->
shortToast(message)
}
.async()
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".demo.main.MainActivity">
android:gravity="center"
android:orientation="vertical"
android:paddingStart="25dp"
android:paddingEnd="25dp">
<TextView
<!--todo: EditText的简写应该是et,不是ed-->
<EditText
android:id="@+id/etPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/main_input_phone_hint"
android:textSize="16sp" />
<EditText
android:id="@+id/etPwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/main_input_password_hint"
android:textSize="16sp" />
<Button
android:id="@+id/btnLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:text="@string/main_login" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
</LinearLayout>
\ No newline at end of file
<resources>
<string name="app_name">mvvm-demo</string>
<!--todo: 项目中显示和toast提示的文案,只要是放在客户端的,都要定义在资源文件中-->
<!--todo: 对于需要拼接显示的文案,使用格式化的方式处理文本-->
<string name="main_input_phone_hint">请输入手机号</string>
<string name="main_input_password_hint">请输入密码</string>
<string name="main_login">登录</string>
<string name="main_please_input_phone">请填写手机号</string>
<string name="main_please_input_password">请填写密码</string>
<string name="main_login_success">登录成功</string>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment