The article contains 2,689 words and will take about 11 minutes to read

A framework that introduces quality open source libraries

Retrofit2 Network request library

Glide picture loading library

BaseRecyclerViewAdapterHelper adapter libraries

Permissionsdispatcher – KTX Permission request library used with Kotlin

MMKV uses Tencent MMKV to replace SharePreference’s high-performance key-value component

SmartRefresh pull-up load pull-down refresh library

Shimmerlayout is a high degree of freedom and easy to use for bone loading

LiveEvent message bus, based on LiveData, has lifecycle awareness

Unpeek-livedata Resolves the LiveData flooding problem

ARouter routing

Disklrucache Hard drive cache

QMUI module is used to introduce QMUI. Since QMUI has many functions, some frequently used functions are extracted separately

You can do this easily

Project structure at a glance

Easy to write, easy to maintain

Class rarely

To write an interface you need to create three files: XML layout, Activity /fragment, ViewModel, and nothing else.

LiveEventBus

Message bus, compared to evnetBus and RxBus, is lifecycle aware and less dependent because it is dependent on Google while RxBus is dependent on RxJava

Liveeventbus.get (constant.del_address_event).post("") liveEventbus.get (constant.event).observe(this, Observer { })Copy the code

Network request code minimalism

Viewmodel Two lines of code do the basic network call and pass the data back to the interface via liveData. The demo illustrates three request scenarios: single entity, receiving NULL, and paging. The structure of paging requests is similar to non-paging, and the code doesn’t need to change much.

A single entity

val singEntityLiveData = StateLiveData<User>()
fun singEntity() = launchUI(
        response = { api.singEntity().data!! },
        liveData = singEntityLiveData
    )
Copy the code

Receive a null

When receiving NULL, StateLiveData writes to Any? ,? Kotlin allows null. Set allowNull to true, otherwise LiveData will not be sent back

val mayNullLiveData = StateLiveData<Any? >().allowNull(true) fun mayNull() = launchUI( response = { api.mayNull().data }, liveData = mayNullLiveData )Copy the code

paging

val pageEntityLiveData = StateLiveData<Page<PageUser>>()
fun pageUser(pageNum: Int) = launchUIPage(
        pageNum,
        response = { api.pageEntity(pageNum).data!! },
        liveData = pageEntityLiveData
    )
Copy the code

Page cache (offline cache)

Requirements:

  • The network request and the read cache must be executed at the same time, not simultaneously.

  • If the network request is faster than the read cache, the cached data cannot overwrite the network request.

  • If there is no network, read the cache, the interface message network error.

  • Each new data overwrites the previously cached data.

fun cacheData() = launchUIPageCache(
        cache = {
            CacheManager.getInstance().getCacheList(
                Constant.CACHE,
                CacheEntity::class.java
            )
        },
        response = { api.cacheEntity().data!! },
        liveData = cacheEntityLiveData
    )
Copy the code

Easy to monitor interface status

Retry retry (the maximum number of retry times can be set individually or globally and conditional or unconditional)

fun singEntity() { launchUI( response = { api.singEntity().data!! }, liveData = singEntityLiveData, error = {e, code -> // Error logic}, retry = {e, RetryUtil(3). RetryToException (e is SocketTimeoutException) {singEntity()}}, Complete = {// interface completes})}Copy the code

Processing interface data

The activity listens for data requested by the interface and processes it

Viewmodel. run {/** ** pageEntityLiveData */ observe(pageEntityLiveData, ::setData).toState(::pageEntityState) /** * Single entity callback */ observe(singEntityLiveData) {}. ToState (::dialogState) /** * */ observe(mayNullLiveData) {}. ToState (::dialogState) /** * Cache callback */ observe(singEntityLiveData) {}.toState(::dialogState) }Copy the code
/** * private fun pageEntityResult(data: Page<PageUser>) {adapter.setpagedata (Page, data.list) page++} / private fun pageEntityState(state: State) { apiState(state, complete = { refresh.complete() }, stateLayout = { adapter.setEmptyView(it) }, hasMore = { refresh.finishMore(it) } ) }Copy the code

Use StateLiveData to listen for status. Complete (),finishMore() and setPageData() are all KT extension methods

/** * @param state status class * @param before interface request * @param complete interface request after (success/failure) * @param hasMore whether there is a next page, @param stateLayout */ fun apiState(state: state, before: () -> Unit)? = null, complete: (() -> Unit)? = null, error: ((code: Int) -> Unit)? = null, hasMore: ((page: Page<*>) -> Unit)? = null, stateLayout: ((layout: Int) -> Unit)? = null ) { when (state.stateCode) { Constant.BEFORE -> before? .invoke() Constant.COMPLETE -> complete? .invoke() Constant.ERROR -> error? .invoke(state.errorCode) Constant.HAS_MORE -> hasMore? .invoke(state.page) Constant.STATE_LAYOUT -> stateLayout? .invoke(state.stateLayout) } }Copy the code

Componentization is supported

Why componentization

When a small function is done very big, for better maintenance and management, separate out to make a component, convenient reuse, separate compilation.

Component technology solutions

Use ARouter+LiveEventBus to realize page jump + component communication

Use viewBinding instead of Kotlin synthetic

Use the activity

private val viewBind by viewBinding(ActivityMainBinding::inflate)
Copy the code

Fragment use (automatically empty when destroying)

private val viewBind by viewBinding(FragmentTestBinding::bind)
Copy the code

Customize many kotlin extension methods (see demo for more methods)

Let’s say you want to load an image

imageView.loadImage("url")
Copy the code

The countdown

Call the lifecycleCountdown method from the Activity/Fragment interface. The value can be determined by 5 parameters: time, interval, start monitoring, progress monitoring and end monitoring.

lifecycleCountdown(10, 1,         
        start = {},
        schedule = {},
        completion = {})
Copy the code

Overriding methods

Rewrite an interface and find that you need to rewrite all of them but only one or two?

viewpager2.setOnPageChangeListener { 
            
}

editText.setOnTextChanged { s, start, before, count ->
            
}
Copy the code

Click on the event

view.onClick{}
Copy the code

Sets the strikeout line for text

textView.centerLine()
Copy the code

Easy skeletal loading

When using skeleton loading with smartRefresh, you may encounter problems with pull-up loading and need to adjust the timing of skeleton loading hide

recyclerView.initSkeletonRecyclerView(adapter)
Copy the code

File download function

fileDownloader(context, downloadUrl,
    completed = {},
    pending = {},
    progress = { soFarBytes, totalBytes ->
		
    })
Copy the code

User Agreement and Privacy Policy show and click hard?

textView.protocol("... User Agreement and Privacy Policy..." ) { when(it){ 0 -> WebViewActivity.start(context,Constant.USER_PROTOCOL) 1 -> WebViewActivity.start(context,Constant.PRIVACY_PROTOCOL) } }Copy the code

Why write your own framework

newbie

I was just starting out, so I don’t know if you have the same Android.

I had no concept of frameworks at the time, and usually projects were directly imported libraries. So all the code is in the Activity, and it’s not well encapsulated. One problem with that is that simple functions require a lot of repetitive code.

As a beginner, many problems need to be solved even if you know the problems.

Skilled workers

After almost a year of this, I started trying to emulate MVC, MVP, MVVM patterns. But I always feel that what I write is not what I want (excessive pursuit of framework standards, design patterns, ideas).

I also often browse the tech post, Github, and find some excellent frameworks like MVPArms, Jetpack-MvVm-best-practice… In this way, I began to understand their framework, logic and ideas little by little and carried out practical projects, such as MVPArms, which I used for two years.

The advanced

As I grew older and more experienced, I began to develop my own code style and methodology.

MVPArms, for example, is a great framework, but it was always written by someone else, so it can’t be 100% true to itself. The framework used Dagger2 and RxJava, both of which were too big, and I only used a few operators for RxJava.

In fact, I think single-function apps are the trend of the future, although there are some super apps (feature aggregation) in China.

Lightweight apps are also more in line with my personal habits, and I usually download the Lite version of the App, regardless of whether it is something or not. Although App functions are single and simple. Aesthetically, simple doesn’t mean mean simple, and every lightweight application deserves consideration.

After thinking about this, I realized that WHAT I needed was a lightweight framework, and since the projects I developed over the years were small and medium-sized, lightweight was probably more suitable for me.

In the future

When I tried to write the process of all kinds of framework, also met a lot of problems, as stated above, at first I standard of excessive pursuit of framework and design patterns, ideas, lead to write framework isn’t have a problem, is very strange, of course, these problems are attributed to my own technical problems, no real understanding framework, didn’t understand the underlying principle.

Later, I reflected on myself, always thinking that these are not the way, can not write framework for the framework, so or a little bit, from simple to simple. In the future, I will continue to learn to conform to Google’s framework and try to write a framework suitable for larger project development. The current plan is that jetpack Compose will be updated into my framework as soon as the stable version is released. If the company is willing, it would also like to practice the project.

I always think that Android should have its own style, rather than follow the style of iOS, or both ends look the same. Before 5.0, Android was really ugly, but after 5.0, when Material Design was added, it felt very different. At that time, I thought that the mainstream software would follow the MD style, but now so many years have passed. Mainstream software still rarely sees MD style. , of course, not to say that the android must conform to standards or MD, it’s just I personally prefer, I think twitter, cables, some of Google’s APP, the experience is very good, in other software may get stuck on my phone, but they don’t card, and the interaction is great, I have joined some MD interface, the framework of the future will add more.

plan

  • 1. Constantly improve the framework to minimize unexpected errors.
  • 2. Try adding new technologies to make the framework more robust without losing weight and simplicity.
  • 3. Add more Material Design styles and interactions (in progress)
  • 4. Try using Jetpack Compose to refactor your layout
  • 5. More need to optimize code
  • 6…

conclusion

I am a person with open source spirit, but I am not strong in technology, so I feel embarrassed to share what I have learned. At the beginning of this line, I knew nothing at that time, so I wrote a blog and a demo when I learned something. I helped some people at that time, but the more experience I had, the more AFRAID I was to write a blog.

Github address: JetPackMVVMLight