Библиотекой был переработан мой старый проект WanAndroid, используя компонент Navigation для создания одноактивного приложения с использованием Fragment, что позволило оптимизировать много кода. В сравнении с предыдущим MVP проектом, производительность разработки и комфорт значительно повысились. Чтобы просмотреть старый MVP проект, переходите на https://github.com/hegaojian/WanAndroid.
Скан QR-кода для скачивания (рекомендовано)
## Как интегрировать
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
...
implementation 'com.github.hegaojian:JetpackMvvm:1.2.7'
}
Для Android Studio версии ниже 4.0 ------>
android {
...
dataBinding {
enabled = true
}
viewBinding {
enabled = true
}
}
Для Android Studio версии 4.0 и выше ------>
android {
...
buildFeatures {
dataBinding = true
viewBinding = true
}
}
Обычно в наших проектах есть набор своих собственных базовых классов, удовлетворяющих бизнес-требованиям — BaseActivity/BaseFragment. Поэтому наш базовый класс должен наследовать базовый класс данной библиотеки.
абстрактный класс BaseActivity<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmDbActivity<VM, DB>() { /** * ID текущего привязанного к активити шаблона представления. Абстрактный метод для реализации в потомках. */ абстрактное переопределение fun layoutId(): Int
/**
* Метод, вызываемый после создания активити. Абстрактный метод для реализации в потомках.
*/
абстрактное переопределение fun initView(savedInstanceState: Bundle?)
/**
* Создание наблюдателя за LiveData.
*/
переопределение fun createObserver()
/**
* Открытие окна ожидания. Здесь можно реализовать отображение вашего окна ожидания.
*/
переопределение fun showLoading(сообщение: String) {
...
}
/**
* Закрытие окна ожидания. Здесь можно реализовать закрытие вашего окна ожидания.
*/
переопределение fun dismissLoading() {
...
}
}
```kotlin
абстрактный класс BaseFragment<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmDbFragment<VM, DB>() {
абстрактное переопределение fun initView(savedInstanceState: Bundle?)
/**
* Ленивая загрузка данных будет вызвана только при отображении фрагмента. Абстрактный метод для реализации в потомках.
*/
абстрактное переопределение fun lazyLoadData()
/**
* Создание наблюдателя за LiveData будет вызвано после ленивой загрузки данных.
*/
переопределение fun createObserver()
/**
* Метод, вызываемый после выполнения onViewCreated фрагмента.
*/
переопределение fun initData() {
}
/**
* Открытие окна ожидания. Здесь можно реализовать отображение вашего окна ожидания.
*/
переопределение fun showLoading(сообщение: String) {
...
}
/**
* Закрытие окна ожидания. Здесь можно реализовать закрытие вашего окна ожидания.
*/
переопределение fun dismissLoading() {
...
}
}
класс LoginViewModel : BaseViewModel() {
}
LoginFragment
, унаследуйте его от базового класса с соответствующими типами параметров. Первый параметр — это ваш LoginViewModel
, второй — ViewDataBinding
. После сохранения файла fragment_login.xml
будет сгенерирован класс FragmentLoginBinding
через data binding (если класс не был создан, попробуйте нажать Build -> Clean Project).class LoginFragment : BaseFragment<LoginViewModel, FragmentLoginBinding>() {
}
``````markdown
## 4. Нетворк-запросы (Retrofit + корутины)
- **4.1 Создание конфигурационного класса запросов наследования от BaseNetworkApi пример:**
```kotlin
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 {
//Пример: добавление общих заголовков, где можно хранить токены, общие параметры и т.д., важно добавить до логического обработчика
addInterceptor(MyHeadInterceptor())
// Логический обработчик
addInterceptor(LogInterceptor())
// Время ожидания соединения, чтения, записи
connectTimeout(10, TimeUnit.SECONDS)
readTimeout(5, TimeUnit.SECONDS)
writeTimeout(5, TimeUnit.SECONDS)
}
return builder
}
}
``````markdown
/**
* Переопределение метода setRetrofitBuilder родительского класса,
* здесь можно произвести любые действия над Retrofit.Builder, такие как добавление GSON парсера, protobuf и т.д.
*/
override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
return builder.apply {
addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
addCallAdapterFactory(CoroutineCallAdapterFactory())
}
}
}
```- **4.2 Если сервер возвращает данные с базовым классом (если нет, то этот шаг можно пропустить):**
```kotlin
{
"data": ...,
"errorCode": 0,
"errorMsg": ""
}
Этот пример представляет собой формат данных, возвращаемых API сайта Play Android. Если errorCode
равен 0, запрос успешен; в противном случае — неудачен.
С точки зрения разработчика, нам требуется получить основные данные (data
) и не выполнять каждую проверку значения errorCode == 0
, чтобы определить успех или неудачу запроса.
В этом случае можно создать базовый класс ответа, который будет наследовать BaseResponse
, реализуя соответствующие методы:
data class ApiResponse<T>(var errorCode: Int, var errorMsg: String, var data: T) : BaseResponse<T>() {
// Этот пример показывает, что успешный запрос на сайте wanandroid имеет значение ошибки равно 0. Пожалуйста, используйте этот код в соответствии с вашими бизнес-требованиями
override fun isSuccess() = errorCode == 0
override fun getResponseCode() = errorCode
override fun getResponseData() = data
override fun getResponseMsg() = errorMsg
}
1. Обертываем данные запроса в ResultState, слушаем ResultState в Activity/Fragment для получения данных и дальнейшей обработки```kotlin class RequestLoginViewModel : BaseViewModel {
// Автоматически распаковывает и фильтрует результат запроса, автоматически проверяет успешность запроса
var loginResult = MutableLiveData<ResultState<UserInfo>>()
// Без помощи фреймворка в распаковке
var loginResult2 = MutableLiveData<ResultState<ApiResponse<UserInfo>>>()
fun login(username: String, password: String) {
// 1. Получаем распакованные данные в слушателе обратного вызова Activity/Fragment (если есть базовый класс)
request(
{ HttpRequestCoroutine.login(username, password) }, // Тело запроса
loginResult, // Приниматель результата запроса, который изменится при любом результате запроса. Следует прослушивать обратные вызовы в Activity или Fragment, подробнее см. обратные вызовы в LoginActivity
true, // Отображение диалогового окна ожидания, по умолчанию false
"Выполняется вход..." // Текст диалогового окна ожидания, можно не указывать
)
// 2. Получаем нераспакованные данные в слушателе обратного вызова Activity/Fragment, вы можете самостоятельно обрабатывать данные в зависимости от кода ответа (если нет базового класса)
requestNoCheck(
{ HttpRequestCoroutine.login(username, password) },
loginResult2,
true,
"Выполняется вход..."
)
}
}
```markdown
class LoginFragment : BaseFragment<LoginViewModel, FragmentLoginBinding>() {
private val requestLoginRegisterViewModel: RequestLoginRegisterViewModel by viewModels()
/**
* Инициализация операций
*/
override fun initView(savedInstanceState: Bundle?) {
...
}
}
``````markdown
## Ленивая загрузка данных фрагмента
```kotlin
/**
* Ленивая загрузка данных фрагмента
*/
override fun lazyLoadData() {
...
}
override fun createObserver() {
// снятие оболочки
requestLoginRegisterViewModel.loginResult.observe(viewLifecycleOwner,
Observer { resultState ->
parseState(resultState, {
// успешный вход в систему, вывод имени пользователя
it.username.logd()
}, {
// неудачный вход в систему (проблемы соединения с сетью, неверный код ответа сервера...)
showMessage(it.errorMsg)
})
})
// без снятия оболочки
requestLoginRegisterViewModel.loginResult2.observe(viewLifecycleOwner, Observer { resultState ->
parseState(resultState, {
if (it.errorCode == 0) {
// успешный вход в систему, вывод имени пользователя
it.data.username.logd()
} else {
// неудачный вход в систему
showMessage(it.errorMsg)
}
}, {
// произошла ошибка запроса
showMessage(it.errorMsg)
})
})
}
class RequestLoginViewModel : BaseViewModel() {
fun login(username: String, password: String) {
//1. Получаем данные после декапсуляции (можно использовать при наличии базового класса проекта)
request({ HttpRequestCoroutine.login(username, password) }, {
//Запрос успешен, автоматически обработано состояние запроса
it.username.logd()
},
{
//Запрос неудачен, вызывается здесь при сетевых проблемах или ошибочных кодах ответа
it.errorMsg.logd()
},
true, "Процесс входа...")
fun login(username: String, password: String) {
//1. Получаем данные после декапсуляции (можно использовать при наличии базового класса проекта)
request({ HttpRequestCoroutine.login(username, password) }, {
//Запрос успешен, автоматически обработано состояние запроса
it.username.logd()
},
{
//Запрос неудачен, вызывается здесь при сетевых проблемах или ошибочных кодах ответа
it.errorMsg.logd()
},
true, "Процесс входа...")
}
``` //2. Получаем данные до декапсуляции, вы можете самостоятельно выполнять бизнес-логику в зависимости от кода ответа (можно использовать при отсутствии базового класса или если вы не хотите использовать эту функцию фреймворка)
requestNoCheck({ HttpRequestCoroutine.login(username, password) }, {
//Запрос успешен, получите данные и выполните бизнес-логику
if (it.error_code == 0) {
//Результат верный
it.data.username.logd()
} else {
//Результат неверный
it.error_msg.logd()
}
},
{
//Запрос неудачен, вызывается здесь при сетевых проблемах
it.error_msg.logd()
},
true, "Процесс входа...")
}
}
val mainViewModel: MainViewModel by viewModels()
или
val mainViewModel: MainViewModel by activityViewModels()
то при необходимости показа сообщения о процессе запроса вам потребуется добавить следующий код в Activity | Fragment:
Установите глобальную переменную jetpackMvvmLog для включения записи логов запросов. По умолчанию значение false
, то есть логи не записываются. Для активации функции записи логов установите значение true
.## 5. Получение ViewModel
val mainViewModel = ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory(application)).get(MainViewModel::class.java)
Сейчас официальный KTX предоставляет расширительные функции для удобства использования
// Получение текущего ViewModel уровня Activity в Activity
private val mainViewModel: MainViewModel by viewModels()
// Получение текущего ViewModel уровня Application в Activity (важно: это предоставляется данным фреймворком, работает только если Application наследует базовый класс BaseApp)
private val mainViewModel by lazy { getAppViewModel<MainViewModel>()}
// В фрагменте получаем ViewModel текущего уровня Fragment
private val mainViewModel: MainViewModel by viewModels()
// В фрагменте получаем ViewModel уровня родительского Activity
private val mainViewModel: MainViewModel by activityViewModels()
// В фрагменте получаем ViewModel уровня приложения Application
private val mainViewModel by lazy { getAppViewModel<MainViewModel>() }
Не буду продолжать, это не так важно. Если хотите подробнее узнать, можете посмотреть в пакетах:
me.hgj.jetpackmvvm.ext.util
me.hgj.jetpackmvvm.ext.view
Вы также можете создать свои расширительные функции в соответствии со своими предпочтениями и требованиями.
-keep class me.hgj.jetpackmvvm.** {*;}
################# ViewBinding & DataBinding #################
-keepclassmembers class * implements androidx.viewbinding.ViewBinding {
public static * inflate(android.view.LayoutInflater);
public static * inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
public static * bind(android.view.View);
}
```## Контакты
- QQ общение: 419581249
## Лицензия
```license
Copyright 2019, hegaojian(Гэ Гаоцзян)
Лицензировано согласно лицензии Apache, версия 2.0 ("Лицензия"); вы не можете использовать этот файл, кроме как в соответствии с Лицензией. Вы можете получить копию Лицензии по адресу:
http://www.apache.org/licenses/LICENSE-2.0
Кроме того, если законом или соглашением в письменной форме не требуется, программное обеспечение, распространяемое в рамках Лицензии, предоставляется «как есть», БЕЗ КАКИХ-ЛИБО УСЛОВИЙ ИЛИ ОГРАНИЧЕНИЙ, явных или подразумеваемых.
Проверьте, пожалуйста, что все необходимые изменения были сделаны.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )