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 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