Commit 2e13cee2 by LouisWang

Initial commit

parents
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/lib-base" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
\ No newline at end of file
/build
\ No newline at end of file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "30.0.0"
defaultConfig {
applicationId "com.example.mymvvmdemo"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
dataBinding {
enabled = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation project(path: ':lib-base')
implementation 'androidx.recyclerview:recyclerview:1.1.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "androidx.core:core-ktx:1.3.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
//BaseAdapter
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.2'
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}
repositories {
mavenCentral()
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.example.mymvvmdemo;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.mymvvmdemo", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mymvvmdemo">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ui.activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activity.ListActivity" />
</application>
</manifest>
\ No newline at end of file
package com.example.mymvvmdemo
import com.example.common.base.BaseApplication
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
class App:BaseApplication() {
override fun onCreate() {
super.onCreate()
}
}
\ No newline at end of file
package com.example.mymvvmdemo.base
import android.app.ProgressDialog
import android.os.Bundle
import android.util.Log
import androidx.databinding.ViewDataBinding
import com.example.common.base.activity.BaseVmActivity
import com.example.common.base.activity.BaseVmDbActivity
import com.example.common.base.viewmodel.BaseViewModel
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
abstract class BaseActivity<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmDbActivity<VM, DB>() {
lateinit var loading: ProgressDialog
abstract override fun layoutId(): Int
abstract override fun initView(savedInstanceState: Bundle?)
abstract override fun createObserver()
override fun showLoading(message: String) {
if (!::loading.isInitialized) {
loading = ProgressDialog(this)
}
loading.show()
Log.e("BaseActivity:", message)
}
override fun dismissLoading() {
if (::loading.isInitialized && loading.isShowing) {
loading.dismiss()
}
Log.e("BaseActivity:", "loading is dismissed")
}
}
\ No newline at end of file
package com.example.mymvvmdemo.base
import android.app.ProgressDialog
import android.os.Bundle
import android.util.Log
import androidx.databinding.ViewDataBinding
import com.example.common.base.fragment.BaseVmDbFragment
import com.example.common.base.viewmodel.BaseViewModel
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
abstract class BaseFragment<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmDbFragment<VM, DB>() {
lateinit var loading: ProgressDialog
/**
* 当前Fragment绑定的视图布局
*/
abstract override fun layoutId(): Int
abstract override fun initView(savedInstanceState: Bundle?)
/**
* 懒加载 只有当前fragment视图显示时才会触发该方法
*/
override fun lazyLoadData() {}
/**
* 创建LiveData观察者 Fragment执行onViewCreated后触发
*/
abstract override fun createObserver()
/**
* Fragment执行onViewCreated后触发
*/
override fun initData() {
}
override fun showLoading(message: String) {
if (!::loading.isInitialized) {
loading = ProgressDialog(mActivity)
}
loading.show()
Log.e("BaseFragment:", "loading is showing")
}
override fun dismissLoading() {
if (::loading.isInitialized && loading.isShowing) {
loading.dismiss()
}
Log.e("BaseFragment:", "loading is dismissed")
}
}
\ No newline at end of file
package com.example.mymvvmdemo.data
/**
* <pre>
* author : louis wang
* time : 2020/06/23
* desc : 列表数据状态类
* version: 1.0
* </pre>
*/
data class ListDataUiState<T>(
//是否请求成功
val isSuccess: Boolean,
//错误消息 isSuccess为false才会有
val errMessage: String = "",
//是否为刷新
val isRefresh: Boolean = false,
//是否为空
val isEmpty: Boolean = false,
//是否还有更多
val hasMore: Boolean = false,
//是第一页且没有数据
val isFirstEmpty:Boolean = false,
//列表数据
val listData: ArrayList<T> = arrayListOf()
)
\ No newline at end of file
package com.example.mymvvmdemo.data
import android.annotation.SuppressLint
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
/**
* <pre>
* author : louis wang
* time : 2020/06/23
* desc :
* version: 1.0
* </pre>
*/
@SuppressLint("ParcelCreator")
@Parcelize
data class ListResponse constructor(var icon: String,
var id: Int,
var link: String,
var name: String,
var order: Int,
var visible: Int) : Parcelable
\ No newline at end of file
package com.example.mymvvmdemo.data
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
@Parcelize
data class UserInfo(var admin: Boolean = false,
var chapterTops: List<String> = listOf(),
var collectIds: MutableList<String> = mutableListOf(),
var email: String = "",
var icon: String = "",
var id: String = "",
var nickname: String = "",
var password: String = "",
var token: String = "",
var type: Int = 0,
var username: String = "") : Parcelable
\ No newline at end of file
package com.example.mymvvmdemo.ext
import androidx.recyclerview.widget.RecyclerView
/**
* <pre>
* author : louis wang
* time : 2020/06/23
* desc :
* version: 1.0
* </pre>
*/
//绑定普通的Recyclerview
fun RecyclerView.init(
layoutManger: RecyclerView.LayoutManager,
bindAdapter: RecyclerView.Adapter<*>,
isScroll: Boolean = true
): RecyclerView {
layoutManager = layoutManger
setHasFixedSize(true)
adapter = bindAdapter
isNestedScrollingEnabled = isScroll
return this
}
package com.example.mymvvmdemo.network
import com.example.common.network.BaseResponse
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
data class ApiResponse<T>(var errorCode: Int, var errorMsg: String, var data: T) : BaseResponse<T>() {
override fun isSuccess() = errorCode == 0
override fun getResponseCode() = errorCode
override fun getResponseData() = data
override fun getResponseMsg() = errorMsg
}
\ No newline at end of file
package com.example.mymvvmdemo.network
import com.example.mymvvmdemo.data.ListResponse
import com.example.mymvvmdemo.data.UserInfo
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
/**
* <pre>
* author : louis wang
* time : 2020/06/18
* desc :
* version: 1.0
* </pre>
*/
interface ApiService {
companion object {
const val SERVER_URL = "https://wanandroid.com/"
}
@FormUrlEncoded
@POST("user/login")
suspend fun login(@Field("username") username: String, @Field("password") pwd: String): ApiResponse<UserInfo>
@GET("friend/json")
suspend fun getCollectUrlData(): ApiResponse<ArrayList<ListResponse>>
}
\ No newline at end of file
package com.example.mymvvmdemo.network
import com.example.mymvvmdemo.data.ListResponse
import com.example.mymvvmdemo.data.UserInfo
/**
* <pre>
* author : louis wang
* time : 2020/06/18
* desc :
* version: 1.0
* </pre>
*/
class HttpRequestManger {
companion object {
val instance: HttpRequestManger by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
HttpRequestManger()
}
}
suspend fun login(username: String, password: String): ApiResponse<UserInfo> {
return NetworkApi.service.login(username, password)
}
suspend fun collectUrlData(): ApiResponse<ArrayList<ListResponse>> {
return NetworkApi.service.getCollectUrlData()
}
}
\ No newline at end of file
package com.example.mymvvmdemo.network
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
/**
* <pre>
* author : louis wang
* time : 2020/06/18
* desc :
* version: 1.0
* </pre>
*/
class MyHeadInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().newBuilder()
// builder.addHeader("token", "token123456").build()
// builder.addHeader("device", "Android").build()
return chain.proceed(builder.build())
}
}
\ No newline at end of file
package com.example.mymvvmdemo.network
import com.example.common.base.BaseApplication
import com.example.common.network.BaseNetworkApi
import com.example.common.network.CoroutineCallAdapterFactory
import com.example.common.network.interceptor.CacheInterceptor
import com.example.common.network.interceptor.logging.LogInterceptor
import com.franmontiel.persistentcookiejar.PersistentCookieJar
import com.franmontiel.persistentcookiejar.cache.SetCookieCache
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor
import com.google.gson.GsonBuilder
import okhttp3.Cache
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.util.concurrent.TimeUnit
/**
* <pre>
* author : louis wang
* time : 2020/06/18
* desc :
* version: 1.0
* </pre>
*/
class NetworkApi : BaseNetworkApi() {
companion object {
val instance: NetworkApi by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
NetworkApi()
}
//双重校验锁式-单例 封装NetApiService 方便直接快速调用
val service: ApiService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
instance.getApi(ApiService::class.java, ApiService.SERVER_URL)
}
}
/**
* 实现重写父类的setHttpClientBuilder方法,
* 在这里可以添加拦截器,可以对 OkHttpClient.Builder 做任意操作
*/
override fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
builder.apply {
//设置缓存配置 缓存最大10M
// cache(Cache(File(BaseApplication.instance.cacheDir, "cache"), 10 * 1024 * 1024))
//添加Cookies自动持久化
// cookieJar(cookieJar)
//示例:添加公共heads 注意要设置在日志拦截器之前,不然Log中会不显示head信息
addInterceptor(MyHeadInterceptor())
//添加缓存拦截器 可传入缓存天数,不传默认7天
// addInterceptor(CacheInterceptor())
// 日志拦截器
addInterceptor(LogInterceptor())
//超时时间 连接、读、写
connectTimeout(10, TimeUnit.SECONDS)
readTimeout(5, TimeUnit.SECONDS)
writeTimeout(5, TimeUnit.SECONDS)
}
return builder
}
/**
* 实现重写父类的setRetrofitBuilder方法,
* 在这里可以对Retrofit.Builder做任意操作,比如添加GSON解析器,protobuf等
*/
override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
return builder.apply {
addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
addCallAdapterFactory(CoroutineCallAdapterFactory())
}
}
val cookieJar: PersistentCookieJar by lazy {
PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(BaseApplication.instance))
}
}
\ No newline at end of file
package com.example.mymvvmdemo.ui.activity
import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.mymvvmdemo.ui.viewmodel.ListViewModel
import com.example.mymvvmdemo.R
import com.example.mymvvmdemo.base.BaseActivity
import com.example.mymvvmdemo.databinding.ListActivityBinding
import com.example.mymvvmdemo.ext.init
import com.example.mymvvmdemo.ui.adapter.ListAdapter
/**
* <pre>
* author : louis wang
* time : 2020/06/23
* desc :
* version: 1.0
* </pre>
*/
class ListActivity : BaseActivity<ListViewModel, ListActivityBinding>() {
override fun layoutId(): Int = R.layout.list_activity
val adapter by lazy { ListAdapter(arrayListOf()) }
override fun initView(savedInstanceState: Bundle?) {
mDataBind.viewModel = mViewModel
mDataBind.swipeRefresh.setOnRefreshListener { mViewModel.getData() }
mDataBind.recycleView.init(LinearLayoutManager(this), adapter).let {
}
adapter.setOnItemClickListener { adapter, view, position ->
}
}
override fun createObserver() {
mViewModel.urlDataState.observe(this, Observer {
mDataBind.swipeRefresh.isRefreshing = false
if (it.isSuccess) {
//成功
when {
//第一页并没有数据 显示空布局界面
it.isEmpty -> {
}
else -> {
adapter.setNewInstance(it.listData)
}
}
} else {
//失败
}
})
}
}
\ No newline at end of file
package com.example.mymvvmdemo.ui.activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.Observer
import com.example.common.ext.parseState
import com.example.mymvvmdemo.ui.viewmodel.MainViewModel
import com.example.mymvvmdemo.R
import com.example.mymvvmdemo.base.BaseActivity
import com.example.mymvvmdemo.databinding.ActivityMainBinding
class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>() {
override fun layoutId(): Int = R.layout.activity_main
override fun initView(savedInstanceState: Bundle?) {
mDataBind.viewModel = mViewModel
mDataBind.btnLogin.setOnClickListener {
when {
mViewModel.phone.value!!.isEmpty() -> {
Toast.makeText(this, "请填写账号", Toast.LENGTH_SHORT).show()
}
mViewModel.pwd.value!!.isEmpty() -> {
Toast.makeText(this, "请填写密码", Toast.LENGTH_SHORT).show()
}
else -> mViewModel.login(
mViewModel.phone.value ?: "",
mViewModel.pwd.value ?: ""
)
}
}
}
override fun createObserver() {
mViewModel.loginResult.observe(this, Observer {
parseState(it, {
Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, ListActivity::class.java))
}, {
//登录失败
Toast.makeText(this, it.errorMsg, Toast.LENGTH_SHORT).show()
})
})
}
}
\ No newline at end of file
package com.example.mymvvmdemo.ui.adapter
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.example.mymvvmdemo.R
import com.example.mymvvmdemo.data.ListResponse
/**
* <pre>
* author : louis wang
* time : 2020/06/23
* desc :
* version: 1.0
* </pre>
*/
class ListAdapter(data: ArrayList<ListResponse>) :
BaseQuickAdapter<ListResponse, BaseViewHolder>(
R.layout.list_item, data
) {
override fun convert(holder: BaseViewHolder, item: ListResponse) {
holder.setText(R.id.tvTitle, item.name)
holder.setText(R.id.tvLink, item.link)
Glide.with(context)
.load(item.icon)
.error(R.mipmap.ic_launcher)
.placeholder(R.mipmap.ic_launcher)
.into(holder.getView(R.id.image))
}
}
\ No newline at end of file
package com.example.mymvvmdemo.ui.viewmodel
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.example.common.base.viewmodel.BaseViewModel
import com.example.common.ext.request
import com.example.mymvvmdemo.data.ListDataUiState
import com.example.mymvvmdemo.data.ListResponse
import com.example.mymvvmdemo.network.HttpRequestManger
/**
* <pre>
* author : louis wang
* time : 2020/06/23
* desc :
* version: 1.0
* </pre>
*/
class ListViewModel(application: Application) : BaseViewModel(application) {
var urlDataState = MutableLiveData<ListDataUiState<ListResponse>>()
override fun onStart() {
super.onStart()
getData()
}
fun getData() {
request({ HttpRequestManger.instance.collectUrlData() }, {
//请求成功
val listDataUiState =
ListDataUiState(
isRefresh = true,
isSuccess = true,
hasMore = false,
isEmpty = it.isEmpty(),
listData = it
)
urlDataState.postValue(listDataUiState)
}, {
//请求失败
val listDataUiState =
ListDataUiState(
isSuccess = false,
errMessage = it.errorMsg,
listData = arrayListOf<ListResponse>()
)
urlDataState.postValue(listDataUiState)
},false)
}
}
\ No newline at end of file
package com.example.mymvvmdemo.ui.viewmodel
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.example.common.base.viewmodel.BaseViewModel
import com.example.common.ext.request
import com.example.common.state.ResultState
import com.example.mymvvmdemo.data.UserInfo
import com.example.mymvvmdemo.network.HttpRequestManger
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
class MainViewModel(application: Application) : BaseViewModel(application) {
var phone = MutableLiveData("")
var pwd = MutableLiveData("")
var loginResult = MutableLiveData<ResultState<UserInfo>>()
fun login(phone: String, pwd: String) {
request(
{ HttpRequestManger.instance.login(phone, pwd) }
, loginResult,
true,
"正在登录中...")
// //在当前Viewmodel中就拿到了脱壳数据数据,做一层封装再给Activity/Fragment
// request({ HttpRequestManger.instance.login(phone, pwd) }, {
//
// }, {
//
// }, true, "数据请求中...")
/*requestNoCheck({HttpRequestManger.instance.login(username,password)},{
//请求成功 自己拿到数据做业务需求操作
if(it.errorCode==0){
//结果正确
}else{
//结果错误
}
},{
//请求失败 网络异常回调在这里
})*/
}
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.mymvvmdemo.ui.viewmodel.MainViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingStart="25dp"
android:paddingEnd="25dp">
<EditText
android:id="@+id/edPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="phone"
android:text="@={viewModel.phone}"
android:textSize="16sp" />
<EditText
android:id="@+id/edPwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="password"
android:text="@={viewModel.pwd}"
android:textSize="16sp" />
<Button
android:id="@+id/btnLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="login" />
<!-- <Button-->
<!-- android:id="@+id/btnRecycle"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="10dp"-->
<!-- android:text="toRecycle" />-->
</LinearLayout>
</layout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.mymvvmdemo.ui.viewmodel.ListViewModel" />
</data>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</layout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="title"
android:textColor="#333333"
android:textSize="14dp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="12dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvLink"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:lines="1"
android:text="www.baidu.com"
android:textColor="#666666"
android:textSize="13dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">MyMvvmDemo</string>
</resources>
\ No newline at end of file
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
\ No newline at end of file
package com.example.mymvvmdemo;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.72'
repositories {
google()
jcenter()
maven {
url 'https://jitpack.io'
}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url 'https://jitpack.io'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
\ No newline at end of file
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
\ No newline at end of file
#Mon Jun 22 10:35:51 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
/build
\ No newline at end of file
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
buildToolsVersion "30.0.0"
defaultConfig {
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
dataBinding {
enabled = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "androidx.core:core-ktx:1.3.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//lifecycle
api 'androidx.lifecycle:lifecycle-runtime:2.2.0'
api 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
// viewModel
api "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
api "androidx.fragment:fragment-ktx:1.2.5"
// liveData
api "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
//retrofit
api "com.squareup.retrofit2:retrofit:2.8.1"
api "com.squareup.retrofit2:converter-gson:2.8.1"
api 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
}
repositories {
mavenCentral()
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.example.common;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.lib_base.test", appContext.getPackageName());
}
}
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.common">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
\ No newline at end of file
package com.example.common.base
import android.app.Application
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
abstract class BaseApplication : Application() {
companion object {
lateinit var instance: BaseApplication
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
\ No newline at end of file
package com.example.common.base.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.common.base.viewmodel.BaseViewModel
import com.example.common.ext.getVmClazz
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 包含ViewModel的Activity基类
* version: 1.0
* </pre>
*/
abstract class BaseVmActivity<VM : BaseViewModel> : AppCompatActivity() {
/**
* 是否需要使用Databinding 供子类BaseVmDbActivity修改
*/
private var isUserDb = false
lateinit var mViewModel: VM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!isUserDb) {
setContentView(layoutId())
} else {
initDataBind()
}
init(savedInstanceState)
}
private fun init(savedInstanceState: Bundle?) {
mViewModel = createViewModel()
lifecycle.addObserver(mViewModel)
registerUiChange()
initView(savedInstanceState)
createObserver()
}
fun userDataBinding(isUserDb: Boolean) {
this.isUserDb = isUserDb
}
/**
* 注册 UI 事件
*/
private fun registerUiChange() {
//显示弹窗
mViewModel.loadingChange.showDialog.observe(this, Observer {
showLoading(it)
})
//关闭弹窗
mViewModel.loadingChange.dismissDialog.observe(this, Observer {
dismissLoading()
})
}
/**
* 创建viewModel
*/
private fun createViewModel(): VM {
return ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory(application)
).get(getVmClazz(this))
}
/**
* 当前绑定的视图布局
*/
abstract fun layoutId(): Int
/**
* 供子类BaseVmDbActivity 初始化Databinding操作
*/
open fun initDataBind() {}
/**
* 初始化view
*/
abstract fun initView(savedInstanceState: Bundle?)
/**
* 创建LiveData数据观察者
*/
abstract fun createObserver()
abstract fun showLoading(message: String = "数据加载中...")
abstract fun dismissLoading()
}
\ No newline at end of file
package com.example.common.base.activity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.example.common.base.viewmodel.BaseViewModel
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 包含ViewModel和Databinding的Activity基类
* version: 1.0
* </pre>
*/
abstract class BaseVmDbActivity<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmActivity<VM>() {
lateinit var mDataBind: DB
override fun onCreate(savedInstanceState: Bundle?) {
userDataBinding(true)
super.onCreate(savedInstanceState)
}
/**
* 创建DataBinding
*/
override fun initDataBind() {
mDataBind = DataBindingUtil.setContentView(this, layoutId())
mDataBind.lifecycleOwner = this
}
override fun onDestroy() {
super.onDestroy()
if (::mDataBind.isInitialized) {
mDataBind.unbind()
}
}
}
\ No newline at end of file
package com.example.common.base.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.example.common.base.viewmodel.BaseViewModel
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 包含ViewModel和Databinding的Fragment基类
* version: 1.0
* </pre>
*/
abstract class BaseVmDbFragment<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmFragment<VM>() {
//该类绑定的ViewDataBinding
lateinit var mDataBind: DB
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mDataBind = DataBindingUtil.inflate(inflater, layoutId(), container, false)
mDataBind.lifecycleOwner = this
return mDataBind.root
}
override fun onDestroy() {
super.onDestroy()
if (::mDataBind.isInitialized) {
mDataBind.unbind()
}
}
}
\ No newline at end of file
package com.example.common.base.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.common.base.viewmodel.BaseViewModel
import com.example.common.ext.getVmClazz
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 包含ViewModel的Fragment基类
* version: 1.0
* </pre>
*/
abstract class BaseVmFragment<VM : BaseViewModel> : Fragment() {
//Fragment的View加载完毕的标记
private var isViewCreated = false
//Fragment对用户可见的标记
private var isUIVisible = false
var isVisibleToUser = false
lateinit var mViewModel: VM
lateinit var mActivity: AppCompatActivity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(layoutId(), container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mActivity = activity as AppCompatActivity
mViewModel = createViewModel()
lifecycle.addObserver(mViewModel)
initView(savedInstanceState)
createObserver()
registerUiChange()
initData()
isViewCreated = true
lazyLoad()
}
/**
* 注册 UI 事件
*/
private fun registerUiChange() {
//显示弹窗
mViewModel.loadingChange.showDialog.observe(this, Observer {
showLoading(it)
})
//关闭弹窗
mViewModel.loadingChange.dismissDialog.observe(this, Observer {
dismissLoading()
})
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
this.isVisibleToUser = isVisibleToUser
//isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见
if (isVisibleToUser) {
isUIVisible = true
lazyLoad()
} else {
isUIVisible = false
}
}
fun lazyLoad() {
//这里进行双重标记判断,是因为setUserVisibleHint会多次回调,并且会在onCreateView执行前回调,必须确保onCreateView加载完毕且页面可见,才加载数据
if (isViewCreated && isUIVisible) {
lazyLoadData()
//数据加载完毕,恢复标记,防止重复加载
isViewCreated = false
isUIVisible = false
}
}
/**
* 创建viewModel
*/
private fun createViewModel(): VM {
return ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(
requireActivity().application)
).get(getVmClazz(this))
}
/**
* 当前Fragment绑定的视图布局
*/
abstract fun layoutId(): Int
/**
* 初始化view
*/
abstract fun initView(savedInstanceState: Bundle?)
/**
* 创建观察者
*/
abstract fun createObserver()
/**
* 懒加载
*/
abstract fun lazyLoadData()
/**
* Fragment执行onCreate后触发的方法
*/
open fun initData() {}
abstract fun showLoading(message: String = "数据加载中...")
abstract fun dismissLoading()
}
\ No newline at end of file
package com.example.common.base.viewmodel
import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.example.common.base.BaseApplication
import com.example.common.event.SingleLiveEvent
import kotlin.reflect.KParameter
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : ViewModel基类
* version: 1.0
* </pre>
*/
open class BaseViewModel(application: Application) : AndroidViewModel(application), IBaseViewModel {
// var context: Context
var hasAttached = true
// constructor(application: Application = BaseApplication.instance) : super(application) {
// this.context = application.applicationContext
// }
val loadingChange: UiLoadingChange by lazy { UiLoadingChange() }
/**
* 内置封装好的可通知Activity/fragment 显示隐藏加载框
*/
inner class UiLoadingChange {
//显示加载框
val showDialog by lazy { SingleLiveEvent<String>() }
//隐藏
val dismissDialog by lazy { SingleLiveEvent<Void>() }
}
override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
}
override fun onCreate() {
hasAttached = true
}
override fun onDestroy() {
hasAttached = false
}
override fun onStart() {
}
override fun onStop() {
}
override fun onResume() {
}
override fun onPause() {
}
}
\ No newline at end of file
package com.example.common.base.viewmodel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
interface IBaseViewModel : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
abstract fun onAny(owner: LifecycleOwner, event: Lifecycle.Event)
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
abstract fun onCreate()
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
abstract fun onDestroy()
@OnLifecycleEvent(Lifecycle.Event.ON_START)
abstract fun onStart()
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
abstract fun onStop()
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
abstract fun onResume()
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
abstract fun onPause()
}
\ No newline at end of file
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.common.event
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
*
*
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
*
*
* Note that only one observer is going to be notified of changes.
*/
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(
"SingleLiveEvent",
"Multiple observers registered but only one will be notified of changes."
)
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
}
package com.example.common.ext
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.example.common.base.activity.BaseVmActivity
import com.example.common.base.activity.BaseVmDbActivity
import com.example.common.base.fragment.BaseVmFragment
import com.example.common.base.viewmodel.BaseViewModel
import com.example.common.network.BaseResponse
import com.example.common.network.error.AppException
import com.example.common.network.error.ExceptionHandle
import com.example.common.state.ResultState
import com.example.common.state.paresException
import com.example.common.state.paresResult
import com.example.common.utils.loge
import kotlinx.coroutines.*
/**
* <pre>
* author : louis wang
* time : 2020/06/18
* desc : BaseViewModel请求协程封装
* version: 1.0
* </pre>
*/
/**
* 显示页面状态,其后两个带默认值的回调可省
* @param resultState 接口返回值
* @param onLoading 加载中
* @param onSuccess 成功回调
* @param onError 失败回调
*
*/
fun <T> BaseVmDbActivity<*, *>.parseState(
resultState: ResultState<T>,
onSuccess: (T) -> Unit,
onError: ((AppException) -> Unit)? = null,
onLoading: (() -> Unit)? = null
) {
when (resultState) {
is ResultState.Loading -> {
showLoading(resultState.loadingMessage)
onLoading?.run { this }
}
is ResultState.Success -> {
dismissLoading()
onSuccess(resultState.data)
}
is ResultState.Error -> {
dismissLoading()
onError?.run { this(resultState.error) }
}
}
}
/**
* 显示页面状态,成功回调在第一个,其后两个带默认值的回调可省
* @param resultState 接口返回值
* @param onLoading 加载中
* @param onSuccess 成功回调
* @param onError 失败回调
*
*/
fun <T> BaseVmActivity<*>.parseState(
resultState: ResultState<T>,
onSuccess: (T) -> Unit,
onError: ((AppException) -> Unit)? = null,
onLoading: (() -> Unit)? = null
) {
when (resultState) {
is ResultState.Loading -> {
showLoading(resultState.loadingMessage)
onLoading?.run { this }
}
is ResultState.Success -> {
dismissLoading()
onSuccess(resultState.data)
}
is ResultState.Error -> {
dismissLoading()
onError?.run { this(resultState.error) }
}
}
}
/**
* 显示页面状态,成功回调在第一个,其后两个带默认值的回调可省
* @param resultState 接口返回值
* @param onLoading 加载中
* @param onSuccess 成功回调
* @param onError 失败回调
*
*/
fun <T> BaseVmFragment<*>.parseState(
resultState: ResultState<T>,
onSuccess: (T) -> Unit,
onError: ((AppException) -> Unit)? = null,
onLoading: (() -> Unit)? = null
) {
when (resultState) {
is ResultState.Loading -> {
showLoading(resultState.loadingMessage)
onLoading?.run { this }
}
is ResultState.Success -> {
dismissLoading()
onSuccess(resultState.data)
}
is ResultState.Error -> {
dismissLoading()
onError?.run { this(resultState.error) }
}
}
}
/**
* net request 不校验请求结果数据是否是成功
* @param block 请求体方法
* @param resultState 请求回调的ResultState数据
* @param isShowDialog 是否显示加载框
* @param loadingMessage 加载框提示内容
*/
fun <T> BaseViewModel.request(
block: suspend () -> BaseResponse<T>,
resultState: MutableLiveData<ResultState<T>>,
isShowDialog: Boolean = false,
loadingMessage: String = "数据请求中..."
): Job {
return viewModelScope.launch {
runCatching {
if (isShowDialog) resultState.value = ResultState.onAppLoading(loadingMessage)
withContext(Dispatchers.IO) { block() }
}.onSuccess {
resultState.paresResult(it)
}.onFailure {
it.message?.loge()
resultState.paresException(it)
}
}
}
/**
* net request 不校验请求结果数据是否是成功
* @param block 请求体方法
* @param resultState 请求回调的ResultState数据
* @param isShowDialog 是否显示加载框
* @param loadingMessage 加载框提示内容
*/
fun <T> BaseViewModel.requestNoCheck(
block: suspend () -> T,
resultState: MutableLiveData<ResultState<T>>,
isShowDialog: Boolean = false,
loadingMessage: String = "数据请求中..."
): Job {
return viewModelScope.launch {
runCatching {
if (isShowDialog) resultState.value = ResultState.onAppLoading(loadingMessage)
withContext(Dispatchers.IO) { block() }
}.onSuccess {
resultState.paresResult(it)
}.onFailure {
it.message?.loge()
resultState.paresException(it)
}
}
}
/**
* 过滤服务器结果,失败抛异常
* @param block 请求体方法,必须要用suspend关键字修饰
* @param success 成功回调
* @param error 失败回调 可不传
* @param isShowDialog 是否显示加载框
* @param loadingMessage 加载框提示内容
*/
fun <T> BaseViewModel.request(
block: suspend () -> BaseResponse<T>,
success: (T) -> Unit,
error: (AppException) -> Unit = {},
isShowDialog: Boolean = false,
loadingMessage: String = "数据请求中..."
): Job {
//如果需要弹窗 通知Activity/fragment弹窗
if (isShowDialog) loadingChange.showDialog.postValue(loadingMessage)
return viewModelScope.launch {
runCatching {
//请求代码块调度在Io线程中
withContext(Dispatchers.IO) { block() }
}.onSuccess {
//网络请求成功 关闭弹窗
loadingChange.dismissDialog.call()
runCatching {
//校验请求结果码是否正确,不正确会抛出异常走下面的onFailure
executeResponse(it) { t -> success(t) }
}.onFailure { e ->
//打印错误消息
e.message?.loge()
//showToast
//失败回调
error(ExceptionHandle.handleException(e))
}
}.onFailure {
//网络请求异常 关闭弹窗
loadingChange.dismissDialog.call()
//打印错误消息
it.message?.loge()
//失败回调
error(ExceptionHandle.handleException(it))
}
}
}
/**
* 不过滤请求结果ExceptionHandle
* @param block 请求体 必须要用suspend关键字修饰
* @param success 成功回调
* @param error 失败回调 可不给
* @param isShowDialog 是否显示加载框
* @param loadingMessage 加载框提示内容
*/
fun <T> BaseViewModel.requestNoCheck(
block: suspend () -> T,
success: (T) -> Unit,
error: (AppException) -> Unit = {},
isShowDialog: Boolean = false,
loadingMessage: String = "数据请求中..."
): Job {
//如果需要弹窗 通知Activity/fragment弹窗
if (isShowDialog) loadingChange.showDialog.postValue(loadingMessage)
return viewModelScope.launch {
runCatching {
//请求时调度在Io线程中
withContext(Dispatchers.IO) { block() }
}.onSuccess {
//网络请求成功 关闭弹窗
loadingChange.dismissDialog.call()
//成功回调
success(it)
}.onFailure {
//网络请求异常 关闭弹窗
loadingChange.dismissDialog.call()
//打印错误消息
it.message?.loge()
//失败回调
error(ExceptionHandle.handleException(it))
}
}
}
/**
* 请求结果过滤,判断请求服务器请求结果是否成功,不成功则会抛出异常
*/
suspend fun <T> executeResponse(
response: BaseResponse<T>,
success: suspend CoroutineScope.(T) -> Unit
) {
coroutineScope {
if (response.isSuccess()) success(response.getResponseData())
else throw AppException(response.getResponseCode(), response.getResponseMsg())
}
}
\ No newline at end of file
package com.example.common.ext
import java.lang.reflect.ParameterizedType
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
/**
* 获取当前类绑定的泛型ViewModel-clazz
*/
@Suppress("UNCHECKED_CAST")
fun <VM> getVmClazz(obj: Any): VM {
return (obj.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as VM
}
\ No newline at end of file
package com.example.common.network
import okhttp3.OkHttpClient
import retrofit2.Retrofit
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 网络请求构建器基类
* version: 1.0
* </pre>
*/
abstract class BaseNetworkApi {
fun <T> getApi(serverClass: Class<T>, baseUrl: String): T {
val retrofitBuilder = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
return setRetrofitBuilder(retrofitBuilder).build().create(serverClass)
}
/**
* 实现重写父类的setHttpClientBuilder方法,
* 在这里可以添加拦截器,可以对 OkHttpClient.Builder 做任意操作
*/
abstract fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder
/**
* 实现重写父类的setRetrofitBuilder方法,
* 在这里可以对Retrofit.Builder做任意操作,比如添加GSON解析器,Protocol
*/
abstract fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder
/**
* 配置http
*/
private val okHttpClient: OkHttpClient
get() {
var builder = OkHttpClient.Builder()
builder = setHttpClientBuilder(builder)
return builder.build()
}
}
\ No newline at end of file
package com.example.common.network
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 服务器返回数据的基类 实现抽象方法,根据自己的业务判断返回请求结果是否成功
* version: 1.0
* </pre>
*/
abstract class BaseResponse<T> {
abstract fun isSuccess(): Boolean
abstract fun getResponseData(): T
abstract fun getResponseCode(): Int
abstract fun getResponseMsg(): String
}
\ No newline at end of file
package com.example.common.network
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import retrofit2.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class CoroutineCallAdapterFactory private constructor() : CallAdapter.Factory() {
companion object {
@JvmStatic
@JvmName("create")
operator fun invoke() = CoroutineCallAdapterFactory()
}
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (Deferred::class.java != getRawType(returnType)) {
return null
}
if (returnType !is ParameterizedType) {
throw IllegalStateException(
"Deferred return type must be parameterized as Deferred<Foo> or Deferred<out Foo>")
}
val responseType = getParameterUpperBound(0, returnType)
val rawDeferredType = getRawType(responseType)
return if (rawDeferredType == Response::class.java) {
if (responseType !is ParameterizedType) {
throw IllegalStateException(
"Response must be parameterized as Response<Foo> or Response<out Foo>")
}
ResponseCallAdapter<Any>(getParameterUpperBound(0, responseType))
} else {
BodyCallAdapter<Any>(responseType)
}
}
private class BodyCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<T>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<T> {
val deferred = CompletableDeferred<T>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
}
private class ResponseCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<Response<T>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Response<T>> {
val deferred = CompletableDeferred<Response<T>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(response)
}
})
return deferred
}
}
}
package com.example.common.network.error
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 自定义错误信息异常
* version: 1.0
* </pre>
*/
class AppException : Exception {
var errorMsg: String //错误消息
var errorCode: Int = 0 //错误码
constructor(errCode: Int, error: String?) : super(error) {
this.errorMsg = error ?: "错误,请稍后再试"
this.errorCode = errCode
}
constructor(error: Error, e: Throwable?) {
errorCode = error.getKey()
errorMsg = error.getValue()
}
}
\ No newline at end of file
package com.example.common.network.error
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
enum class Error(private val code: Int, private val error: String) {
ERROR_NET(-1, "网络异常"),
ERROR_CONNECT(-3, "无法连接到服务器"),
ERROR_TIMEOUT(-4, "请求超时"),
ERROR_SERVER(-5, "错误,请稍后再试"),
ERROR_DATA(-6, "错误,请稍后再试"),
ERROR_HANDLE(-7, "错误,请稍后再试"),
ERROR_UNKNOWN(-8, "错误,请稍后再试");
fun getValue(): String {
return error
}
fun getKey(): Int {
return code
}
}
\ No newline at end of file
package com.example.common.network.error
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 根据异常返回相关的错误信息
* version: 1.0
* </pre>
*/
object ExceptionHandle {
fun handleException(e: Throwable?): AppException {
return when (e) {
is UnknownHostException -> AppException(Error.ERROR_NET, e)
is ConnectException -> AppException(Error.ERROR_CONNECT, e)
is SocketTimeoutException -> AppException(Error.ERROR_TIMEOUT, e)
is ServerException -> AppException(Error.ERROR_SERVER, e)
is NullPointerException -> AppException(Error.ERROR_DATA, e)
else -> AppException(Error.ERROR_UNKNOWN, e)
}
}
}
\ No newline at end of file
package com.example.common.network.error
import java.io.IOException
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 服务器异常
* version: 1.0
* </pre>
*/
class ServerException(message: String) : IOException(message) {
}
\ No newline at end of file
package com.example.common.network.interceptor
import com.example.common.base.BaseApplication
import com.example.common.utils.NetworkUtil
import okhttp3.CacheControl
import okhttp3.Interceptor
import okhttp3.Response
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :缓存拦截器 默认7天
* version: 1.0
* </pre>
*/
class CacheInterceptor(var day: Int = 7) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (!NetworkUtil.isNetworkAvailable(BaseApplication.instance)) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
}
val response = chain.proceed(request)
if (!NetworkUtil.isNetworkAvailable(BaseApplication.instance)) {
val maxAge = 60 * 60
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=$maxAge")
.build()
} else {
val maxStale = 60 * 60 * 24 * day // tolerate 4-weeks stale
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
.build()
}
return response
}
}
\ No newline at end of file
package com.example.common.network.interceptor.logging;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.example.common.utils.CharacterHandler;
import com.example.common.utils.LogUtils;
import java.util.List;
import okhttp3.MediaType;
import okhttp3.Request;
/**
* 作者 : hegaojian
* 时间 : 2020/3/26
* 描述 :
*/
public class DefaultFormatPrinter implements FormatPrinter {
private static final String TAG = "HttpLog";
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String DOUBLE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR;
private static final String[] OMITTED_RESPONSE = {LINE_SEPARATOR, "Omitted response body"};
private static final String[] OMITTED_REQUEST = {LINE_SEPARATOR, "Omitted request body"};
private static final String N = "\n";
private static final String T = "\t";
private static final String REQUEST_UP_LINE = " ┌────── Request ────────────────────────────────────────────────────────────────────────";
private static final String END_LINE = " └───────────────────────────────────────────────────────────────────────────────────────";
private static final String RESPONSE_UP_LINE = " ┌────── Response ───────────────────────────────────────────────────────────────────────";
private static final String BODY_TAG = "Body:";
private static final String URL_TAG = "URL: ";
private static final String METHOD_TAG = "Method: @";
private static final String HEADERS_TAG = "Headers:";
private static final String STATUS_CODE_TAG = "Status Code: ";
private static final String RECEIVED_TAG = "Received in: ";
private static final String CORNER_UP = "┌ ";
private static final String CORNER_BOTTOM = "└ ";
private static final String CENTER_LINE = "├ ";
private static final String DEFAULT_LINE = "│ ";
private static final String[] ARMS = new String[]{"-A-", "-R-", "-M-", "-S-"};
private static ThreadLocal<Integer> last = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
private static boolean isEmpty(String line) {
return TextUtils.isEmpty(line) || N.equals(line) || T.equals(line) || TextUtils.isEmpty(line.trim());
}
/**
* 对 {@code lines} 中的信息进行逐行打印
*
* @param tag
* @param lines
* @param withLineSize 为 {@code true} 时, 每行的信息长度不会超过110, 超过则自动换行
*/
private static void logLines(String tag, String[] lines, boolean withLineSize) {
for (String line : lines) {
int lineLength = line.length();
int maxLongSize = withLineSize ? 110 : lineLength;
for (int i = 0; i <= lineLength / maxLongSize; i++) {
int start = i * maxLongSize;
int end = (i + 1) * maxLongSize;
end = end > line.length() ? line.length() : end;
LogUtils.debugInfo(resolveTag(tag), DEFAULT_LINE + line.substring(start, end));
}
}
}
private static String computeKey() {
if (last.get() >= 4) {
last.set(0);
}
String s = ARMS[last.get()];
last.set(last.get() + 1);
return s;
}
/**
* 此方法是为了解决在 AndroidStudio v3.1 以上 Logcat 输出的日志无法对齐的问题
* <p>
* 此问题引起的原因, 据 JessYan 猜测, 可能是因为 AndroidStudio v3.1 以上将极短时间内以相同 tag 输出多次的 log 自动合并为一次输出
* 导致本来对称的输出日志, 出现不对称的问题
* AndroidStudio v3.1 此次对输出日志的优化, 不小心使市面上所有具有日志格式化输出功能的日志框架无法正常工作
* 现在暂时能想到的解决方案有两个: 1. 改变每行的 tag (每行 tag 都加一个可变化的 token) 2. 延迟每行日志打印的间隔时间
* <p>
* {@link #resolveTag(String)} 使用第一种解决方案
*
* @param tag
*/
private static String resolveTag(String tag) {
return computeKey() + tag;
}
private static String[] getRequest(Request request) {
String log;
String header = request.headers().toString();
log = METHOD_TAG + request.method() + DOUBLE_SEPARATOR +
(isEmpty(header) ? "" : HEADERS_TAG + LINE_SEPARATOR + dotHeaders(header));
return log.split(LINE_SEPARATOR);
}
private static String[] getResponse(String header, long tookMs, int code, boolean isSuccessful,
List<String> segments, String message) {
String log;
String segmentString = slashSegments(segments);
log = ((!TextUtils.isEmpty(segmentString) ? segmentString + " - " : "") + "is success : "
+ isSuccessful + " - " + RECEIVED_TAG + tookMs + "ms" + DOUBLE_SEPARATOR + STATUS_CODE_TAG +
code + " / " + message + DOUBLE_SEPARATOR + (isEmpty(header) ? "" : HEADERS_TAG + LINE_SEPARATOR +
dotHeaders(header)));
return log.split(LINE_SEPARATOR);
}
private static String slashSegments(List<String> segments) {
StringBuilder segmentString = new StringBuilder();
for (String segment : segments) {
segmentString.append("/").append(segment);
}
return segmentString.toString();
}
/**
* 对 {@code header} 按规定的格式进行处理
*
* @param header
* @return
*/
private static String dotHeaders(String header) {
String[] headers = header.split(LINE_SEPARATOR);
StringBuilder builder = new StringBuilder();
String tag = "─ ";
if (headers.length > 1) {
for (int i = 0; i < headers.length; i++) {
if (i == 0) {
tag = CORNER_UP;
} else if (i == headers.length - 1) {
tag = CORNER_BOTTOM;
} else {
tag = CENTER_LINE;
}
builder.append(tag).append(headers[i]).append("\n");
}
} else {
for (String item : headers) {
builder.append(tag).append(item).append("\n");
}
}
return builder.toString();
}
private static String getTag(boolean isRequest) {
if (isRequest) {
return TAG + "-Request";
} else {
return TAG + "-Response";
}
}
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 可以解析的情况
*
* @param request
* @param bodyString
*/
@Override
public void printJsonRequest(@NonNull Request request, @NonNull String bodyString) {
final String requestBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString;
final String tag = getTag(true);
LogUtils.debugInfo(tag, REQUEST_UP_LINE);
logLines(tag, new String[]{URL_TAG + request.url()}, false);
logLines(tag, getRequest(request), true);
logLines(tag, requestBody.split(LINE_SEPARATOR), true);
LogUtils.debugInfo(tag, END_LINE);
}
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 为 {@code null} 或不可解析的情况
*
* @param request
*/
@Override
public void printFileRequest(@NonNull Request request) {
final String tag = getTag(true);
LogUtils.debugInfo(tag, REQUEST_UP_LINE);
logLines(tag, new String[]{URL_TAG + request.url()}, false);
logLines(tag, getRequest(request), true);
logLines(tag, OMITTED_REQUEST, true);
LogUtils.debugInfo(tag, END_LINE);
}
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 可以解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param contentType 服务器返回数据的数据类型
* @param bodyString 服务器返回的数据(已解析)
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
@Override
public void printJsonResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers, @Nullable MediaType contentType,
@Nullable String bodyString, @NonNull List<String> segments, @NonNull String message, @NonNull final String responseUrl) {
bodyString = LogInterceptor.isJson(contentType) ? CharacterHandler.jsonFormat(bodyString)
: LogInterceptor.isXml(contentType) ? CharacterHandler.xmlFormat(bodyString) : bodyString;
final String responseBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString;
final String tag = getTag(false);
final String[] urlLine = {URL_TAG + responseUrl, N};
LogUtils.debugInfo(tag, RESPONSE_UP_LINE);
logLines(tag, urlLine, true);
logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true);
logLines(tag, responseBody.split(LINE_SEPARATOR), true);
LogUtils.debugInfo(tag, END_LINE);
}
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 为 {@code null} 或不可解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
@Override
public void printFileResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers,
@NonNull List<String> segments, @NonNull String message, @NonNull final String responseUrl) {
final String tag = getTag(false);
final String[] urlLine = {URL_TAG + responseUrl, N};
LogUtils.debugInfo(tag, RESPONSE_UP_LINE);
logLines(tag, urlLine, true);
logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true);
logLines(tag, OMITTED_RESPONSE, true);
LogUtils.debugInfo(tag, END_LINE);
}
}
package com.example.common.network.interceptor.logging;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
import okhttp3.MediaType;
import okhttp3.Request;
/**
* 作者 : hegaojian
* 时间 : 2020/3/26
* 描述 :
*/
public interface FormatPrinter {
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 可以解析的情况
*
* @param request
* @param bodyString 发送给服务器的请求体中的数据(已解析)
*/
void printJsonRequest(@NonNull Request request, @NonNull String bodyString);
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 为 {@code null} 或不可解析的情况
*
* @param request
*/
void printFileRequest(@NonNull Request request);
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 可以解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param contentType 服务器返回数据的数据类型
* @param bodyString 服务器返回的数据(已解析)
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
void printJsonResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers, @Nullable MediaType contentType,
@Nullable String bodyString, @NonNull List<String> segments, @NonNull String message, @NonNull String responseUrl);
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 为 {@code null} 或不可解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
void printFileResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers,
@NonNull List<String> segments, @NonNull String message, @NonNull String responseUrl);
}
\ No newline at end of file
package com.example.common.network.interceptor.logging;
import android.util.Log;
import androidx.annotation.Nullable;
import com.example.common.utils.CharacterHandler;
import com.example.common.utils.UrlEncoderUtil;
import com.example.common.utils.ZipHelper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
public class LogInterceptor implements Interceptor {
private FormatPrinter mPrinter = new DefaultFormatPrinter();
private Level printLevel = Level.ALL;
public LogInterceptor() { }
public LogInterceptor(Level printLevel) {
}
/**
* 解析请求服务器的请求参数
*
* @param request {@link Request}
* @return 解析后的请求信息
* @throws UnsupportedEncodingException
*/
public static String parseParams(Request request) throws UnsupportedEncodingException {
try {
RequestBody body = request.newBuilder().build().body();
if (body == null) {
return "";
}
Buffer requestbuffer = new Buffer();
body.writeTo(requestbuffer);
Charset charset = Charset.forName("UTF-8");
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
String json = requestbuffer.readString(charset);
if (UrlEncoderUtil.hasUrlEncoded(json)) {
json = URLDecoder.decode(json, convertCharset(charset));
}
return CharacterHandler.jsonFormat(json);
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
/**
* 是否可以解析
*
* @param mediaType {@link MediaType}
* @return {@code true} 为可以解析
*/
public static boolean isParseable(MediaType mediaType) {
if (mediaType == null || mediaType.type() == null) {
return false;
}
return isText(mediaType) || isPlain(mediaType)
|| isJson(mediaType) || isForm(mediaType)
|| isHtml(mediaType) || isXml(mediaType);
}
public static boolean isText(MediaType mediaType) {
if (mediaType == null || mediaType.type() == null) {
return false;
}
return "text".equals(mediaType.type());
}
public static boolean isPlain(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("plain");
}
public static boolean isJson(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("json");
}
public static boolean isXml(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("xml");
}
public static boolean isHtml(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("html");
}
public static boolean isForm(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("x-www-form-urlencoded");
}
public static String convertCharset(Charset charset) {
String s = charset.toString();
int i = s.indexOf("[");
if (i == -1) {
return s;
}
return s.substring(i + 1, s.length() - 1);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
boolean logRequest = printLevel == Level.ALL || (printLevel != Level.NONE && printLevel == Level.REQUEST);
if (logRequest) {
//打印请求信息
if (request.body() != null && isParseable(request.body().contentType())) {
mPrinter.printJsonRequest(request, parseParams(request));
} else {
mPrinter.printFileRequest(request);
}
}
boolean logResponse = printLevel == Level.ALL || (printLevel != Level.NONE && printLevel == Level.RESPONSE);
long t1 = logResponse ? System.nanoTime() : 0;
Response originalResponse;
try {
originalResponse = chain.proceed(request);
} catch (Exception e) {
Log.d("Http Error: %s",e.getMessage());
throw e;
}
long t2 = logResponse ? System.nanoTime() : 0;
ResponseBody responseBody = originalResponse.body();
//打印响应结果
String bodyString = null;
if (responseBody != null && isParseable(responseBody.contentType())) {
bodyString = printResult(request, originalResponse, logResponse);
}
if (logResponse) {
final List<String> segmentList = request.url().encodedPathSegments();
final String header;
if (originalResponse.networkResponse() == null) {
header = originalResponse.headers().toString();
} else {
header = originalResponse.networkResponse().request().headers().toString();
}
final int code = originalResponse.code();
final boolean isSuccessful = originalResponse.isSuccessful();
final String message = originalResponse.message();
final String url = originalResponse.request().url().toString();
if (responseBody != null && isParseable(responseBody.contentType())) {
mPrinter.printJsonResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1), isSuccessful,
code, header, responseBody.contentType(), bodyString, segmentList, message, url);
} else {
mPrinter.printFileResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1),
isSuccessful, code, header, segmentList, message, url);
}
}
return originalResponse;
}
/**
* 打印响应结果
*
* @param request {@link Request}
* @param response {@link Response}
* @param logResponse 是否打印响应结果
* @return 解析后的响应结果
* @throws IOException
*/
@Nullable
private String printResult(Request request, Response response, boolean logResponse) throws IOException {
try {
//读取服务器返回的结果
ResponseBody responseBody = response.newBuilder().build().body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
//获取content的压缩类型
String encoding = response
.headers()
.get("Content-Encoding");
Buffer clone = buffer.clone();
//解析response content
return parseContent(responseBody, encoding, clone);
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
/**
* 解析服务器响应的内容
*
* @param responseBody {@link ResponseBody}
* @param encoding 编码类型
* @param clone 克隆后的服务器响应内容
* @return 解析后的响应结果
*/
private String parseContent(ResponseBody responseBody, String encoding, Buffer clone) {
Charset charset = Charset.forName("UTF-8");
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
//content 使用 gzip 压缩
if ("gzip".equalsIgnoreCase(encoding)) {
//解压
return ZipHelper.decompressForGzip(clone.readByteArray(), convertCharset(charset));
} else if ("zlib".equalsIgnoreCase(encoding)) {
//content 使用 zlib 压缩
return ZipHelper.decompressToStringForZlib(clone.readByteArray(), convertCharset(charset));
} else {
//content 没有被压缩, 或者使用其他未知压缩方式
return clone.readString(charset);
}
}
public enum Level {
/**
* 不打印log
*/
NONE,
/**
* 只打印请求信息
*/
REQUEST,
/**
* 只打印响应信息
*/
RESPONSE,
/**
* 所有数据全部打印
*/
ALL
}
}
package com.example.common.state
import androidx.lifecycle.MutableLiveData
import com.example.common.network.BaseResponse
import com.example.common.network.error.AppException
import com.example.common.network.error.ExceptionHandle
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc : 自定义结果集封装类
* version: 1.0
* </pre>
*/
sealed class ResultState<out T> {
companion object {
fun <T> onAppSuccess(data: T): ResultState<T> = Success(data)
fun <T> onAppLoading(loadingMessage:String): ResultState<T> = Loading(loadingMessage)
fun <T> onAppError(error: AppException): ResultState<T> = Error(error)
}
data class Loading(val loadingMessage:String) : ResultState<Nothing>()
data class Success<out T>(val data: T) : ResultState<T>()
data class Error(val error: AppException) : ResultState<Nothing>()
}
/**
* 处理返回值
* @param result 请求结果
*/
fun <T> MutableLiveData<ResultState<T>>.paresResult(result: BaseResponse<T>) {
value = if (result.isSuccess()) ResultState.onAppSuccess(result.getResponseData()) else
ResultState.onAppError(AppException(result.getResponseCode(), result.getResponseMsg()))
}
/**
* 不处理返回值 直接返回请求结果
* @param result 请求结果
*/
fun <T> MutableLiveData<ResultState<T>>.paresResult(result: T) {
value = ResultState.onAppSuccess(result)
}
/**
* 异常转换异常处理
*/
fun <T> MutableLiveData<ResultState<T>>.paresException(e: Throwable) {
this.value = ResultState.onAppError(ExceptionHandle.handleException(e))
}
\ No newline at end of file
package com.example.common.utils;
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class CharacterHandler {
private CharacterHandler() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* json 格式化
*
* @param json
* @return
*/
public static String jsonFormat(String json) {
if (TextUtils.isEmpty(json)) {
return "Empty/Null json content";
}
String message;
try {
json = json.trim();
if (json.startsWith("{")) {
JSONObject jsonObject = new JSONObject(json);
message = jsonObject.toString(4);
} else if (json.startsWith("[")) {
JSONArray jsonArray = new JSONArray(json);
message = jsonArray.toString(4);
} else {
message = json;
}
} catch (JSONException e) {
message = json;
} catch (OutOfMemoryError error) {
message = "Output omitted because of Object size";
}
return message;
}
/**
* xml 格式化
*
* @param xml
* @return
*/
public static String xmlFormat(String xml) {
if (TextUtils.isEmpty(xml)) {
return "Empty/Null xml content";
}
String message;
try {
Source xmlInput = new StreamSource(new StringReader(xml));
StreamResult xmlOutput = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(xmlInput, xmlOutput);
message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n");
} catch (TransformerException e) {
message = xml;
}
return message;
}
}
package com.example.common.utils
import android.util.Log
/**
* <pre>
* author : louis wang
* time : 2020/06/22
* desc :
* version: 1.0
* </pre>
*/
const val TAG = "mvvm"
var showLog = true
private enum class LEVEL {
V, D, I, W, E
}
fun String.logv(tag: String = TAG) =
log(LEVEL.V, tag, this)
fun String.logd(tag: String = TAG) =
log(LEVEL.D, tag, this)
fun String.logi(tag: String = TAG) =
log(LEVEL.I, tag, this)
fun String.logw(tag: String = TAG) =
log(LEVEL.W, tag, this)
fun String.loge(tag: String = TAG) =
log(LEVEL.E, tag, this)
private fun log(level: LEVEL, tag: String, message: String) {
if (!showLog) return
when (level) {
LEVEL.V -> Log.v(tag, message)
LEVEL.D -> Log.d(tag, message)
LEVEL.I -> Log.i(tag, message)
LEVEL.W -> Log.w(tag, message)
LEVEL.E -> Log.e(tag, message)
}
}
\ No newline at end of file
package com.example.common.utils;
import android.text.TextUtils;
import android.util.Log;
public class LogUtils {
private static final String DEFAULT_TAG = "mvvm";
private static boolean isLog = true;
private LogUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
public static boolean isLog() {
return isLog;
}
public static void setLog(boolean isLog) {
LogUtils.isLog = isLog;
}
public static void debugInfo(String tag, String msg) {
if (!isLog || TextUtils.isEmpty(msg)) {
return;
}
Log.d(tag, msg);
}
public static void debugInfo(String msg) {
debugInfo(DEFAULT_TAG, msg);
}
public static void warnInfo(String tag, String msg) {
if (!isLog || TextUtils.isEmpty(msg)) {
return;
}
Log.w(tag, msg);
}
public static void warnInfo(String msg) {
warnInfo(DEFAULT_TAG, msg);
}
/**
* 这里使用自己分节的方式来输出足够长度的 message
*
* @param tag 标签
* @param msg 日志内容
*/
public static void debugLongInfo(String tag, String msg) {
if (!isLog || TextUtils.isEmpty(msg)) {
return;
}
msg = msg.trim();
int index = 0;
int maxLength = 3500;
String sub;
while (index < msg.length()) {
if (msg.length() <= index + maxLength) {
sub = msg.substring(index);
} else {
sub = msg.substring(index, index + maxLength);
}
index += maxLength;
Log.d(tag, sub.trim());
}
}
public static void debugLongInfo(String msg) {
debugLongInfo(DEFAULT_TAG, msg);
}
}
package com.example.common.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.util.Enumeration;
public class NetworkUtil {
public static String url = "http://www.baidu.com";
/**
* NetworkAvailable
*/
public static int NET_CNNT_BAIDU_OK = 1;
/**
* no NetworkAvailable
*/
public static int NET_CNNT_BAIDU_TIMEOUT = 2;
/**
* Net no ready
*/
public static int NET_NOT_PREPARE = 3;
/**
* net error
*/
public static int NET_ERROR = 4;
/**
* TIMEOUT
*/
private static int TIMEOUT = 3000;
/**
* check NetworkAvailable
*
* @param context
* @return
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager manager = (ConnectivityManager) context.getApplicationContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
if (null == manager) {
return false;
}
NetworkInfo info = manager.getActiveNetworkInfo();
if (null == info || !info.isAvailable())
return false;
return true;
}
/**
* getLocalIpAddress
*
* @return
*/
public static String getLocalIpAddress() {
String ret = "";
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
ret = inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
ex.printStackTrace();
}
return ret;
}
/**
* 返回当前网络状态
*
* @param context
* @return
*/
public static int getNetState(Context context) {
try {
ConnectivityManager connectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo networkinfo = connectivity.getActiveNetworkInfo();
if (networkinfo != null) {
if (networkinfo.isAvailable() && networkinfo.isConnected()) {
if (!connectionNetwork()) {
return NET_CNNT_BAIDU_TIMEOUT;
} else {
return NET_CNNT_BAIDU_OK;
}
} else {
return NET_NOT_PREPARE;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return NET_ERROR;
}
/**
* ping "http://www.baidu.com"
*
* @return
*/
static private boolean connectionNetwork() {
boolean result = false;
HttpURLConnection httpUrl = null;
try {
httpUrl = (HttpURLConnection) new URL(url)
.openConnection();
httpUrl.setConnectTimeout(TIMEOUT);
httpUrl.connect();
result = true;
} catch (IOException e) {
} finally {
if (null != httpUrl) {
httpUrl.disconnect();
}
httpUrl = null;
}
return result;
}
/**
* check is3G
*
* @param context
* @return boolean
*/
public static boolean is3G(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetInfo != null
&& activeNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
return true;
}
return false;
}
/**
* isWifi
*
* @param context
* @return boolean
*/
public static boolean isWifi(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetInfo != null
&& activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return true;
}
return false;
}
/**
* is2G
*
* @param context
* @return boolean
*/
public static boolean is2G(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetInfo != null
&& (activeNetInfo.getSubtype() == TelephonyManager.NETWORK_TYPE_EDGE
|| activeNetInfo.getSubtype() == TelephonyManager.NETWORK_TYPE_GPRS || activeNetInfo
.getSubtype() == TelephonyManager.NETWORK_TYPE_CDMA)) {
return true;
}
return false;
}
/**
* is wifi on
*/
public static boolean isWifiEnabled(Context context) {
ConnectivityManager mgrConn = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
TelephonyManager mgrTel = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
return ((mgrConn.getActiveNetworkInfo() != null && mgrConn
.getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED) || mgrTel
.getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS);
}
}
package com.example.common.utils;
public class UrlEncoderUtil {
private UrlEncoderUtil() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 判断 str 是否已经 URLEncoder.encode() 过
* 经常遇到这样的情况, 拿到一个 URL, 但是搞不清楚到底要不要 URLEncoder.encode()
* 不做 URLEncoder.encode() 吧, 担心出错, 做 URLEncoder.encode() 吧, 又怕重复了
*
* @param str 需要判断的内容
* @return 返回 {@code true} 为被 URLEncoder.encode() 过
*/
public static boolean hasUrlEncoded(String str) {
boolean encode = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '%' && (i + 2) < str.length()) {
// 判断是否符合urlEncode规范
char c1 = str.charAt(i + 1);
char c2 = str.charAt(i + 2);
if (isValidHexChar(c1) && isValidHexChar(c2)) {
encode = true;
break;
} else {
break;
}
}
}
return encode;
}
/**
* 判断 c 是否是 16 进制的字符
*
* @param c 需要判断的字符
* @return 返回 {@code true} 为 16 进制的字符
*/
private static boolean isValidHexChar(char c) {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
}
}
package com.example.common.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
public class ZipHelper {
private ZipHelper() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* zlib decompress 2 String
*
* @param bytesToDecompress
* @return
*/
public static String decompressToStringForZlib(byte[] bytesToDecompress) {
return decompressToStringForZlib(bytesToDecompress, "UTF-8");
}
/**
* zlib decompress 2 String
*
* @param bytesToDecompress
* @param charsetName
* @return
*/
public static String decompressToStringForZlib(byte[] bytesToDecompress, String charsetName) {
byte[] bytesDecompressed = decompressForZlib
(
bytesToDecompress
);
String returnValue = null;
try {
returnValue = new String
(
bytesDecompressed,
0,
bytesDecompressed.length,
charsetName
);
} catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return returnValue;
}
/**
* zlib decompress 2 byte
*
* @param bytesToDecompress
* @return
*/
public static byte[] decompressForZlib(byte[] bytesToDecompress) {
byte[] returnValues = null;
Inflater inflater = new Inflater();
int numberOfBytesToDecompress = bytesToDecompress.length;
inflater.setInput
(
bytesToDecompress,
0,
numberOfBytesToDecompress
);
int bufferSizeInBytes = numberOfBytesToDecompress;
int numberOfBytesDecompressedSoFar = 0;
List<Byte> bytesDecompressedSoFar = new ArrayList<Byte>();
try {
while (inflater.needsInput() == false) {
byte[] bytesDecompressedBuffer = new byte[bufferSizeInBytes];
int numberOfBytesDecompressedThisTime = inflater.inflate
(
bytesDecompressedBuffer
);
numberOfBytesDecompressedSoFar += numberOfBytesDecompressedThisTime;
for (int b = 0; b < numberOfBytesDecompressedThisTime; b++) {
bytesDecompressedSoFar.add(bytesDecompressedBuffer[b]);
}
}
returnValues = new byte[bytesDecompressedSoFar.size()];
for (int b = 0; b < returnValues.length; b++) {
returnValues[b] = (byte) (bytesDecompressedSoFar.get(b));
}
} catch (DataFormatException dfe) {
dfe.printStackTrace();
}
inflater.end();
return returnValues;
}
/**
* zlib compress 2 byte
*
* @param bytesToCompress
* @return
*/
public static byte[] compressForZlib(byte[] bytesToCompress) {
Deflater deflater = new Deflater();
deflater.setInput(bytesToCompress);
deflater.finish();
byte[] bytesCompressed = new byte[Short.MAX_VALUE];
int numberOfBytesAfterCompression = deflater.deflate(bytesCompressed);
byte[] returnValues = new byte[numberOfBytesAfterCompression];
System.arraycopy
(
bytesCompressed,
0,
returnValues,
0,
numberOfBytesAfterCompression
);
return returnValues;
}
/**
* zlib compress 2 byte
*
* @param stringToCompress
* @return
*/
public static byte[] compressForZlib(String stringToCompress) {
byte[] returnValues = null;
try {
returnValues = compressForZlib
(
stringToCompress.getBytes("UTF-8")
);
} catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return returnValues;
}
/**
* gzip compress 2 byte
*
* @param string
* @return
* @throws IOException
*/
public static byte[] compressForGzip(String string) {
ByteArrayOutputStream os = null;
GZIPOutputStream gos = null;
try {
os = new ByteArrayOutputStream(string.length());
gos = new GZIPOutputStream(os);
gos.write(string.getBytes("UTF-8"));
byte[] compressed = os.toByteArray();
return compressed;
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(gos);
closeQuietly(os);
}
return null;
}
/**
* gzip decompress 2 string
*
* @param compressed
* @return
* @throws IOException
*/
public static String decompressForGzip(byte[] compressed) {
return decompressForGzip(compressed, "UTF-8");
}
/**
* gzip decompress 2 string
*
* @param compressed
* @param charsetName
* @return
*/
public static String decompressForGzip(byte[] compressed, String charsetName) {
final int BUFFER_SIZE = compressed.length;
GZIPInputStream gis = null;
ByteArrayInputStream is = null;
try {
is = new ByteArrayInputStream(compressed);
gis = new GZIPInputStream(is, BUFFER_SIZE);
StringBuilder string = new StringBuilder();
byte[] data = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = gis.read(data)) != -1) {
string.append(new String(data, 0, bytesRead, charsetName));
}
return string.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(gis);
closeQuietly(is);
}
return null;
}
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
}
package com.example.common;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
include ':lib-base'
include ':app'
rootProject.name = "MyMvvmDemo"
\ 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