1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/hegaojian-JetpackMvvm

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Платформа Лицензия GitHub

:chicken::chicken::chicken:JetPackMvvm

  • Интеграция компонентной библиотеки JetPack от Google, основанной на паттерне MVVM: LiveData, ViewModel, Lifecycle, Navigation
  • Использует язык Kotlin с множеством расширений, что упрощает код
  • Добавлен Retrofit для сетевых запросов, асинхронные задачи, чтобы упростить различные операции и сделать быстрее запросы к сети

Демо приложения

Библиотекой был переработан мой старый проект WanAndroid, используя компонент Navigation для создания одноактивного приложения с использованием Fragment, что позволило оптимизировать много кода. В сравнении с предыдущим MVP проектом, производительность разработки и комфорт значительно повысились. Чтобы просмотреть старый MVP проект, переходите на https://github.com/hegaojian/WanAndroid.

Примеры экранов

Экраны приложения

Скачивание APK:

## Как интегрировать

  • 1.1 Добавьте Jitpack репозиторий в root's build.gradle
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
  • 1.2 Добавьте зависимость в app's build.gradle
dependencies {
  ...
  implementation 'com.github.hegaojian:JetpackMvvm:1.2.7'
}
  • 1.3 Включите DataBinding и ViewBinding в модуле android вашего app's build.gradle
Для Android Studio версии ниже 4.0 ------>

android {
    ...
    dataBinding {
        enabled = true
    }
    viewBinding {
        enabled = true
    }
}

Для Android Studio версии 4.0 и выше ------>

android {
    ...
    buildFeatures {
        dataBinding = true
        viewBinding = true
    }
}

2. Наследование базового класса

Обычно в наших проектах есть набор своих собственных базовых классов, удовлетворяющих бизнес-требованиям — BaseActivity/BaseFragment. Поэтому наш базовый класс должен наследовать базовый класс данной библиотеки.

  • Если вы не хотите использовать DataBinding и ViewBinding — можете наследовать BaseVmActivity/BaseVmFragment.
  • Если вы используете DataBinding — можете наследовать BaseVmDbActivity/BaseVmDbFragment.
  • Если вы используете ViewBinding — можете наследовать BaseVmVbActivity/BaseVmVbFragment.

Activity:```kotlin

абстрактный класс 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() {
        ...
    }
}

3. Реализация функционала входа в систему

  • 3.1 Создайте класс LoginViewModel, который наследуется от BaseViewModel.

класс LoginViewModel : BaseViewModel() {
  
}
  • 3.2 Создайте 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
}
  • 4.3 В ViewModel отправляем запросы, все запросы запускаются в viewModelScope, они выполняются в IO потоке, а обратный вызов происходит в главном потоке. Когда страница уничтожается, все запросы отменяются, поэтому нет необходимости беспокоиться о утечках памяти. Фреймворк предлагает два способа использования запросов

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

2. Получение результатов запроса непосредственно в текущем ViewModel

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, "Процесс входа...")
    }

}

Внимание: При использовании данного способа запроса следует учитывать, что если данный ViewModel не связан с Activity/Fragment через шаблон Generic ViewModel, а получен следующими способами:

val mainViewModel: MainViewModel by viewModels()
или
val mainViewModel: MainViewModel by activityViewModels()

то при необходимости показа сообщения о процессе запроса вам потребуется добавить следующий код в Activity | Fragment:

addLoadingObserve(viewModel)

4.4 Включение записи логов

Установите глобальную переменную jetpackMvvmLog для включения записи логов запросов. По умолчанию значение false, то есть логи не записываются. Для активации функции записи логов установите значение true.## 5. Получение ViewModel

  • 5.1 Наши Activity/Fragment могут иметь несколько 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>() }

6. Написал несколько часто используемых расширительных функций

Не буду продолжать, это не так важно. Если хотите подробнее узнать, можете посмотреть в пакетах:
me.hgj.jetpackmvvm.ext.util
me.hgj.jetpackmvvm.ext.view
Вы также можете создать свои расширительные функции в соответствии со своими предпочтениями и требованиями.

7. Обфускация

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

Вы можете оставить комментарий после Вход в систему

Введение

Быстрый фреймворк для разработки на основе Jetpack и паттерна MVVM. Интегрирует официально рекомендованную Google библиотеку компонентов JetPack: LiveData, ViewModel, компоненты жизненного цикла (Lifecycle). Написан на Kotlin с использованием большого количества функций расширения, что упрощает код. Включает Retrofit для сетевых запросов, а т... Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/hegaojian-JetpackMvvm.git
git@api.gitlife.ru:oschina-mirror/hegaojian-JetpackMvvm.git
oschina-mirror
hegaojian-JetpackMvvm
hegaojian-JetpackMvvm
master