preface

PokemonGo Jetpack is based on the MVVM architecture and Repository design model. PokemonGo is based on the MVVM architecture and Repository design model. The techniques used in PokemonGo are all related to the knowledge points in the previous series of articles. Paging3 (Network + DB), Dagger Hilt, App Startup, DataBinding, Room, Motionlayout, Kotlin Flow, Coil, JProgressView, etc.

PokemonGo has been uploaded to the making of the project: https://github.com/hi-dhl/PokemonGo, welcome to view, dynamic rendering as shown below, if the figure can’t see, please click here to view the dynamic rendering | static figure

The Jetpack real game PokemonGo includes the following features:

  1. Use a custom RemoteMediator to mix network and DB (RemoteMediator is an important member of Paging3)
  2. Use Data Mapper to separate the Data source and UI
  3. Mixed use of Kotlin Flow with Retrofit2 + Room
  4. Use of Kotlin Flow with LiveData
  5. Load the image using Coil
  6. Work together using ViewModel, LiveData, and DataBinding
  7. Use Motionlayout to animate
  8. Use of App Startup and Hilt

PokemonGo technology:

  • Gradle Versions Plugin: Checks whether the latest version of the dependency library exists
  • Kotlin + Coroutines + Flow: Flow is an extension of Kotlin Coroutines that allows us to run asynchronous code just as we run synchronous code
  • JetPack
    • Paging3 (network + db) : use Paging3RemoteMediatorUsed to implement network + DB
    • Dagger Hilt (2.28-alpha) : Dependency injection framework
    • App Startup: Sets the component initialization sequence
    • DataBinding: Declaratively binds observable data to an interface
    • Room: Provides an abstraction layer on TOP of SQLite for smooth access to SQLite databases
    • LiveData: Notifies the view when the underlying database changes
    • ViewModel: Manages data related to the interface in a lifecycle focused manner
    • Android KTX: Write more concise, idiomatic Kotlin code
  • The project architecture
    • The MVVM architecture
    • Repository Design pattern
    • Data Mapper Data mapping
  • Retrofit2&okhttp3: Used to request network data
  • Coil: The first image loading library based on Kotlin
  • Material – Components – Android: Modular and customizable material design UI components
  • Motionlayout: Motionlayout is a layout type that helps you manage animations in your application
  • Timber: Logs are printed
  • JProgressView: A small, flexible, customizable progress bar that supports graphics: circles, rounded rectangles, rectangles, and more

The above technology stack corresponds to the previous technical article:

  • Jetpack’s newest member, AndroidX App Startup, practices and principles
  • Jetpack Member Paging3 Practices and Source Code Analysis (I)
  • Paging3, A New member of Jetpack (Part 2)
  • New member of Jetpack Hilt Practice (1) Journey through the pit
  • An advanced part of App Startup (ii) by Hilt, a new member of Jetpack
  • New member of Jetpack Hilt and Dagger big difference (3) Landing Piece
  • The performance of Hilt and Koin was analyzed comprehensively
  • [原 文] Give up Dagger Koin
  • Encapsulate Kotlin + Android Databinding in the project
  • Kotlin technique and principle analysis that few people know (1)
  • Kotlin technique and principle analysis that few people know (2)

If there is no contact for these technologies before, or just heard that there is no effect on reading this article, this article will analysis on these technologies combined with a project PokemonGo to to the simplicity of the article, this article does not scrutinize technical details, since each technology need to take several articles to analysis is clear, I will be in a subsequent article to detailed analysis.

How do I check the latest version of a dependency library

In my analysis so far, there are approximately four different ways to manage Gradle dependencies:

  • Manual management: Define plug-in dependencies in each module and manually change them each time you upgrade them (not recommended)
  • Manage plug-in dependency libraries the Ext way: This is Google’s recommended method for managing dependencies
  • Kotlin + buildSrc: Auto-complete and click jump, rebuilding the entire project when relying on updates
  • Composing Builds: Auto-complete and one-click jumps that rely on updates without rebuilding an entire project

The new Version of AndroidStudio only supports ext and manual management to check for the latest version of the dependency libraries. BuildSrc and Gradle-Wrapper versions are not supported.

Can not meet the needs of PokemonGo project, in The PokemonGo project to use the buildSrc way to manage all the dependent libraries, because PokemonGo project using a single module structure, and support automatic completion and click jump is very convenient, The Gradle Versions Plugin is used to check the latest version of the dependency library. The check result is as follows:

The following dependencies have later release versions: - androidx. Swiperefreshlayout: swiperefreshlayout [1.0.0 - > 1.1.0] https://developer.android.com/jetpack/androidx - Com. Squareup. Okhttp3: logging - interceptor [3.9.0 - > 4.7.2] https://square.github.io/okhttp/ - junit: junit [4.12 - > 4.13] http://junit.org -org. koin:koin-android [2.1.5 -> 2.1.6] -org. koin: koin-Androidx-viewModel [2.1.5 -> 2.1.6] - Org. koin:koin-core [2.1.5 -> 2.1.6] Gradle release-candidate updates: -gradle: [6.1.1 -> 6.5.1]Copy the code

It lists the latest Versions of all dependencies that need to be updated, and the Gradle Versions Plugin is more comprehensive than AndroidStudio supports:

  • Supports manual management of checking the latest version of dependency libraries
  • Supports ext’s approach to managing the latest version checking of dependency libraries
  • Supports the buildSrc mode to manage the latest version check of dependency libraries
  • Support for checking the latest version of Gradle-Wrapper
  • Support for checking the latest version of multi-module dependency libraries

So how to use it? It only takes three steps

  • 1. Copy the checkVersions. Gradle file to your PokemonGo root directory

  • 2. Add the following code to the build.gradle folder at the root of your project

    apply from: './checkVersions.gradle' buildscript { repositories { google() jcenter() } dependencies { classpath "Com. Making. Ben - manes: gradle versions - the plugin: 0.28.0"}}Copy the code
  • 3. Run the following command in the root directory.

    ./gradlew dependencyUpdates
    Copy the code

    In the current directory to generate the build/dependencyUpdates/report. TXT file.

The MVVM architecture

PokemonGo is based on the MVVM architecture and Repository design model. Almost all Android developers have heard of the MVVM architecture at least by now. After the Google Android team announced Jetpack’s viewmodel, It has become one of the most popular architectures for modern Android development, as shown below:

MVVM helps to completely separate the application’s business logic from the UI. If the business logic is closely linked to the UI logic, maintenance is difficult, writing unit test code is difficult because it is difficult to reuse the business logic, a lot of repetitive code and complex logic.

The MVVM architecture for Jetpack’s ViewModel consists of View + DataBinding + ViewModel + Model.

DataBinding

DataBinding is actually another level of view structure in the XML layout, and the view (XML) is constantly interacting with the ViewModel through the DataBinding layer.

RecyclerView is used to display Pokemon data (names, images, click events, etc.), and there is a ViewHolder for each item.

class PokemonViewModel(view: View) : DataBindingViewHolder<PokemonListModel>(view) {
    private val mBinding: RecycleItemPokemonBinding by viewHolderBinding(view)

    override fun bindData(data: PokemonListModel, position: Int) {
        mBinding.apply {
            pokemon = data
            executePendingBindings()
        }
    }

}
Copy the code

As you can see, the code inside the ViewHolder is much simpler due to the use of data binding. In case this example isn’t obvious enough, let’s take a look at one of the more exciting ones. Clicking on the home page of each item will take you to the details page, as shown below:

The DetailActivity page shows the detailed data of Pokemon, first query the database, if not found, read the network data and save to the database, because of the use of data binding, the code becomes very simple, as shown below:

class DetailActivity : DataBindingAppCompatActivity() {

    private val mBindingActivity: ActivityDetailsBinding by binding(R.layout.activity_details)
    private val mViewModel: DetailViewModel by viewModels()
    lateinit var mPokemonModel: PokemonListModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBindingActivity.apply {
            mPokemonModel = requireNotNull(intent.getParcelableExtra(KEY_LIST_MODEL))
            pokemonListModel = mPokemonModel
            lifecycleOwner = this@DetailActivity
            viewModel = mViewModel.apply {
                fectchPokemonInfo(mPokemonModel.name)
                    .observe(this@DetailActivity, Observer {})
            }
        }
    }
}
Copy the code

As you can see, the DetailActivity code is very simple, and we don’t need to change any of the DetailActivity code if we want to change the URL of the network, the Model, how data is fetched or saved, and so on.

For more information about the use of DataBinding, please refer to my other warehouse, JDataBinding: There are a number of components that have been encapsulated DataBindingActivity, DataBindingAppCompatActivity, DataBindingFragmentActivity, DataBindingFragment, DataBindingDialog, DataB IndingListAdapter, DataBindingViewHolder and so on.

ViewModel

ViewModel is a very important design in the MVVM architecture. It plays a very important role in activities or fragments and business logic. It does not rely on UI components, making unit testing easier. The ViewModel manages the data associated with the interface in a lifecycle manner until the Activity is destroyed.

LiveData works well with the ViewModel. LiveData holds the data retrieved from the data source, and it can be observed by the DataBinding component. When the Activity is destroyed, it will be unsubscribed.

The DetailActivity code is so simple because ViewModel, LiveData, and DataBinding work together. Let’s take a look at the ViewModel code.

class DetailViewModel @ViewModelInject constructor( val polemonRepository: Repository ) : ViewModel() { private val _pokemon = MutableLiveData<PokemonInfoModel>() val pokemon: LiveData<PokemonInfoModel> = _pokemon @OptIn(ExperimentalCoroutinesApi::class) fun fectchPokemonInfo(name: String) = liveData<PokemonInfoModel> { polemonRepository.featchPokemonInfo(name) .collectLatest { _pokemon.postValue(it)  emit(it) } ....... // Omit part of the code,}}Copy the code

Activity_details. XML code

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="com.hi.dhl.pokemon.ui.detail.DetailViewModel" />

    </data>
    
    ......
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/weight"
        android:text="@{viewModel.pokemon.getWeightString}"/>
    ......
    
</layout>
Copy the code

This is about getting detailed information about the Pokemon, using DataBinding to declaratively bind the data (the weight of the Pokemon) to the interface, using more code from the reference project.

Repository

Repository design pattern is one of the most popular and most widely used design patterns, in the Repository layer to obtain network data, and the data stored in the database, In this layer are two very important members of the Paging3 library RemoteMediator and Data Mappers.

RemoteMediator

In previous articles, Jetpack member Paging3 Practices and Source Analysis (I) and Jetpack New Member Paging3 Networking Practices and Principles (II) analyzed using Paging3 to access databases and networks, respectively. What is missing is the use of the RemoteMediator class, a very important member of Paging3 that implements database and network access, so this is a complement to the previous article.

RemoteMediator is very important, need to spend a separate article to analyze, in order to save space, here will not analyze it in detail, if you do not understand the RemoteMediator does not matter, I will analyze it in detail in the following articles.

In the project, Retrofit2 and OkHttp3 are used for network access to request network data, and Room is used as a database store to save the obtained data to the database. Room provides an abstraction layer on SQLite, which enables smooth access to SQLite database. It also has all the functionality of SQLite, error checking at compile time.

@OptIn(ExperimentalPagingApi::class) class PokemonRemoteMediator( val api: PokemonService, val db: AppDataBase ) : RemoteMediator<Int, PokemonEntity>() { val mPageKey = 0 override suspend fun load( loadType: LoadType, state: PagingState<Int, PokemonEntity> ): MediatorResult { try { ...... Val pageKey = the when (loadType) {/ / PagingDataAdapter first visit or call the refresh () loadType. Refresh - > null / / in the current load data sets at the beginning of the loading data Loadtype. PREPEND -> return mediatorResult. Success(endOfPaginationReached = true) loadType. APPEND -> { . if (remoteKey == null || remoteKey.nextKey == null) { return MediatorResult.Success(endOfPaginationReached = true) } remoteKey.nextKey } } ...... Val page = pageKey? Val page = pageKey? : 0 val result = api.fetchPokemonList( state.config.pageSize, page * state.config.pageSize ).results ....... Db.withtransaction {if (loadType == loadType.refresh) {// Clear the current data when the loadType is first loaded, or when the pull-down REFRESH occurs}...... Remotekeysdao.insertall (entity) Pokemondao.insertPokemon (item)} return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) } catch (e: IOException) { return MediatorResult.Error(e) } catch (e: HttpException) { return MediatorResult.Error(e) } } }Copy the code

Note: Used@OptIn(ExperimentalPagingApi::class)You need to add the following code to the App module build.gradle file.

android {
    kotlinOptions {
        freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
    }
}
Copy the code

In the RemoteMediator implementation class PokemonRemoteMediator in the core part is about the parameter LoadType judgment.

  • LoadType.REFRESH:The first visit toOr callPagingDataAdapter.refresh(). There is no need to do anything here, just return null
  • LoadType.PREPEND: when data is added to the head of the current list, it is rarely used in actual projectsMediatorResult.Success(endOfPaginationReached = true)The parameter endOfPaginationReached indicates that there is no data left to load
  • LoadType.APPENDIf the key does not exist, it indicates that there is no more data and is directly returnedMediatorResult.Success(endOfPaginationReached = true)Network and database access will not be performed

The next logic is not much different from the previous logic for requesting network data, using Retrofit2 to get the network data, and then using Room to save the data to the database.

Another important member of Repository, the Data Mapper, plays a very important role in the project. In a rapid development project, the Data source and UI are subconsciously bound together in order to complete the first release as quickly as possible. As the business increases and the Data source changes, The top layer also has to change along with it, resulting in a lot of refactoring later, and the core cause is too coupled.

Data Mapper (Personal suggestion)

Data Mapper’s awareness is very important and plays a very important role in the project. The “Real” Repository Pattern in Android gets 4.9k likes on Medium for The importance of Data Mappers in Repository.

Use Data Mapper to separate the Model of the Data source and the Model of the page display. Do not modify the upper page with the addition, modification or deletion of the Data source. In other words, use Data Mapper to make an intermediate transformation, as shown in the figure below, from the network:

The advantages of using Data Mapper are as follows:

  • Changes to data sources do not affect upper-layer business
  • A bad back-end implementation doesn’t affect the upper-level business (imagine if you were forced to perform 2 network requests because the back end couldn’t provide all the information you needed in one request, would you let this affect your entire code?)
  • Data Mapper facilitates unit testing to ensure that upper-layer services will not be affected by Data source changes

If you use Data Mapper directly in a large project, it will backfire, so it needs to be improved with design patterns, which is beyond the scope of this article. In fact, what I want to say here is that you should not subconsciously bind the Data source model to the UI because of the quick implementation of a function.

Data Mappe can be implemented in a variety of ways. It can be implemented manually or by introducing a third party framework, one of which is known as ModelMapper, which is implemented manually in PokemonGo.

Kotlin Flow

Stop using RxJava and try Flow, which is simple and powerful, and both Retrofit2 and Room provide support for it.

Flow is a new library added after the release of Kotlin Coroutines 1.3.2. Also known as asynchronous Flows, Flow is a similar RxJava Observable that is used in PokemonGo.

override suspend fun featchPokemonInfo(name: String): Flow<PokemonInfoModel> { return flow { val pokemonDao = db.pokemonInfoDao() var infoModel = pokemonDao.getPokemon(name) If there is no request network if (infoModel == null) {// Network request val netWorkPokemonInfo = api.fetchPokemonInfo(name)...... } emit(Model) = mapper2Infomodel. map(infoModel) // emit(model) }.flowOn(Dispatchers.IO) }Copy the code

Three things have been done here:

  • Check whether the database exists. If not, the request network exists
  • Request network to obtain data, update database
  • Convert the Model of the data source to the Model displayed on the page

Dependency injection

Hilt Dagger, Koin, etc., are all DEPENDENCY injection libraries. Using dependency injection libraries has the following advantages:

  • The dependency injection library automatically releases objects that are no longer used, reducing resource overuse.
  • Re-using dependencies and creating instances within the scope of configuring scopes increases the reusability of the code and reduces much of the template code.
  • The code becomes more readable.
  • Easy to build objects.
  • Write low-coupling code that is easier to test.

Hilt is used in PokemonGo. Hilt builds on the Dagger to reduce manual dependency in the project. Hilt integrates the Jetpack library with the Android framework class and removes most of the template code. Hilt also has the Dagger benefits, compile-time correctness, runtime performance, and support from Android Studio. Take a look at an example of Hilt working with Room.

@ the Module @ InstallIn (ApplicationComponent: : class) object RoomModule {/ * * * @ Provides used to be @ the Module annotation tag inside of a class of methods, and Provides the object dependencies. * @provides @Singleton */ @provides @Singleton Fun provideAppDataBase(Application: Application): AppDataBase { return Room .databaseBuilder(application, AppDataBase::class.java, "dhl.db") .fallbackToDestructiveMigration() .allowMainThreadQueries() .build() } @Singleton @Provides fun provideTasksRepository( db: AppDataBase ): Repository { return PokemonFactory.makePokemonRepository(db) } }Copy the code

Here you need to use @Module annotation, use @Module annotation general class, inside the Room instance, more use can see PokemonGo project.

Small and flexible progress bar

The Progress bar in the Pokemon details page uses JProgressView, a small, flexible and customizable progress bar that supports shapes such as circles, rounded rectangles, rectangles, and more.

Originated in thought with a ready-made libraries, but to find on the Internet a lot, not a right, not a lot, or the authors haven’t updated for a long time, or are not compatible DataBinding, so he draw on his own packaging on a small flexible bar, long-term maintenance of project and continue to update, if you have better advice welcome told me, JProgressView is very simple to use and can be configured according to your needs.

<com.hi.dhl.jprogressview.JProgressView
    android:layout_width="match_parent"
    android:layout_height="18dp"
    android:layout_below="@+id/exp"
    android:translationZ="100dp"
    app:maxProgressValue="@{viewModel.pokemon.maxExp}"
    app:progressValue="@{viewModel.pokemon.exp}"
    app:progress_animate_duration="@integer/progress_animate_duration"
    app:progress_color="@color/color_progress_4"
    app:progress_color_background="@color/color_progress_bg"
    app:progress_paint_bg_width="@dimen/circle_stroke_width"
    app:progress_paint_value_width="@dimen/circle_stroke_width"
    app:progress_text_color="@android:color/black"
    app:progress_text_size="@dimen/text_size_12sp"
    app:progress_type="@integer/porgress_tpye_round_rect" />
Copy the code
The name of the Value types The default value note
progress_type integer Round: 1. Rectangle: 0; Rectangle: 0; Rectangle: 0
progress_animate_duration integer 2000 Animation run time
progress_color color Color.GRAY Current Progress Color
progress_color_background color Color.GRAY Background color of the progress bar
progress_paint_bg_width dimen 10 Width of the progress bar background brush
progress_paint_value_width dimen 10 Width of the current progress brush
progress_text_color color Color.BLUE The color of the text on the progress bar
progress_text_size dimen sp2Px(20f) Size of the text on the progress bar
progress_text_visible boolean False is not displayed by default Whether to display text
progress_value integer 0 The current progress
progress_value_max integer 100 Maximum value of the current progress bar

For more information on the use of progress bars, check out the JProgressView repository. This is the end of the article. To save space, many of the details that have been covered in the previous series will not be covered here, and more technical details will be covered in a future series.

We are building a complete and up-to-date project of AndroidX Jetpack related components and related component theory analysis articles, which already includes App Startup, Paging3, Hilt, etc., we are gradually adding other new Jetpack members, and the warehouse is continuously updated. Check it out: AndroidX-Jetpack-Practice, if this helps you, please give me a “like” in the upper right corner of the warehouse.

conclusion

Committed to sharing a series of Android source code, reverse analysis, algorithm, translation, Jetpack source code related articles, is working hard to write a better article, if this article is helpful to you to a STAR, what is not written clearly in the article, or what is better advice welcome to leave a message, welcome to learn together, Together on the road to technology.

algorithm

Due to the huge question bank of LeetCode, hundreds of questions can be screened for each category. Due to the limited energy of everyone, it is impossible to complete all the questions. Therefore, I classified the questions according to the classical types and sorted the questions according to their difficulty.

  • Data structures: arrays, stacks, queues, strings, lists, trees…
  • Algorithms: search algorithms, search algorithms, bit operations, sorting, mathematics,…

Each problem is implemented in Java and Kotlin, and each problem has a solution idea, time complexity and space complexity. If you like algorithms and LeetCode as MUCH as I do, you can follow my LeetCode problem on GitHub: Leetcode-solutions-with-java-and-kotlin, let’s learn And look forward to growing with you.

Android 10 source code series

I am writing a series of Android 10 source code analysis articles. Understanding the system source code is not only helpful for analyzing problems, but also very helpful for us in the interview process. If you like studying Android source code like I do, Keep an eye on my GitHub android 10-Source-Analysis repository, where articles will be synced.

  • Android 10 source code analysis: How is APK generated
  • 0xA02 Android 10 Source code Analysis: APK installation process
  • 0xA03 Android 10 Source code Analysis: APK load process resource loading
  • 0xA04 Android 10 source code Analysis: APK Load Process resource Load (2)
  • 0xA05 Android 10 source code analysis: Dialog load drawing process and use in Kotlin, DataBinding
  • 0xA06 Android 10 Source code Analysis: WindowManager View binding and architecture
  • 0xA07 Android 10 source code analysis: Window type and 3D view hierarchical analysis
  • More……

Android Apps

  • How to encapsulate Kotlin + Android Databinding in a project
  • Goodbye buildSrc, embrace Composing Builds to improve Android build speed
  • Kotlin’s technique and principle analysis that few people know about
  • Jetpack’s newest member, AndroidX App Startup, practices and principles
  • Jetpack Member Paging3 Practices and Source Code Analysis (I)
  • Paging3, A New member of Jetpack (Part 2)
  • New member of Jetpack Hilt Practice (1) Journey through the pit
  • An advanced part of App Startup (ii) by Hilt, a new member of Jetpack
  • New member of Jetpack Hilt and Dagger big difference (3) Landing Piece
  • The performance of Hilt and Koin was analyzed comprehensively

Select a translation

At present, we are organizing and translating a series of selected foreign technical articles, not only for translation, but also many excellent English technical articles provide good ideas and methods. Each article will have a translator’s thinking part, which provides a more in-depth interpretation of the original text. Follow my Technical-Article-Translation on GitHub, and the articles will be synchronized to this repository.

  • Google engineers have just released a new feature for Fragments, “A new way to transfer data between Fragments” and source analysis
  • How to use Koin and some source code analysis with FragmentFactory
  • [2.4k Start] Drop Dagger Koin
  • [5K +] Kotlin performance optimization those things
  • Decrypt RxJava’s exception handling mechanism
  • [翻 译][1.4k + Star] Kotlin rookie Coil VS Glide and Picasso
  • More……

Tool series

  • The few people who know about AndroidStudio shortcuts (1)
  • The few people who know about AndroidStudio shortcuts (part 2)
  • What you need to know about the ADB command
  • 10 minutes to get started with Shell scripting
  • Android Studio dynamically debugs the APP based on Smali files
  • The Android Device Monitor tool cannot be found in Android Studio 3.2