GithubHelp home page GithubHelp logo

line / lich Goto Github PK

View Code? Open in Web Editor NEW
181.0 181.0 22.0 1.06 MB

A library collection that enhances the development of Android apps.

License: Apache License 2.0

Kotlin 99.89% Thrift 0.11%
android kotlin kotlin-android okhttp thrift

lich's People

Contributors

dtvc87 avatar francais-harry avatar ganadist avatar yamasa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lich's Issues

Investigate the crash occurring in GitHub Actions

In GitHub Actions, :viewmodel-test-mockk:connectedDebugAndroidTest crashes frequently with the following messages.

> Task :viewmodel-test-mockk:connectedDebugAndroidTest
Starting 4 tests on test(AVD) - 10

com.linecorp.lich.viewmodel.test.mockk.MockingTest > mockViewModel[test(AVD) - 10] FAILED 
Test failed to run to completion. Reason: 'Instrumentation run failed due to 'Process crashed.''. Check device logcat for details
Tests on test(AVD) - 10 failed: Instrumentation run failed due to 'Process crashed.'

> Task :viewmodel-test-mockk:connectedDebugAndroidTest FAILED

But, this crash cannot be reproduced on my Pixel devices. :(

connectedAndroidTest is broken on AGP 7.2

After upgrading AGP from 7.1.2 to 7.2.2, gradlew connectedAndroidTest fails with the following errors.

com.linecorp.lich.component.ActivityTest > activityLazy[Pixel_5_API_32(AVD) - 12] FAILED 
        java.lang.RuntimeException: Unable to resolve activity for: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.linecorp.lich.component.test/com.linecorp.lich.component.ComponentTestActivity }
        at androidx.test.core.app.InstrumentationActivityInvoker.startActivity(InstrumentationActivityInvoker.java:402)
Tests on Pixel_5_API_32(AVD) - 12 failed: There was 1 failure(s).

[Improvement] DefaultComponentProvider could be use AutoService instead of using META-INF

In the current implementation, the DefaultComponentProvider is not utilizing AutoService to register the ServiceLocator. Adding manual registration always error-prone if we have multiple ServiceLocator components. In order to automate the process, we can install AutoService to DefaultComponentProvider

Potential changes from

internal class DefaultComponentProvider : ComponentProvider 

to

@AutoService(ComponentProvider::class)
internal class DefaultComponentProvider : ComponentProvider {

The content type of thrift packet should never be hard-coded

On the latest snapshot of lich-thrift at this moment, in ThriftRequestBody, the content type of the request message is hard-coded like:, in ThriftRequestBody, the content type of the request message is hard-coded like:

private val mediaTypeThrift: MediaType = "application/x-thrift".toMediaType()

However, according to the Armeria document and the armeria API server's actual behavior,
application/x-thrift uses the TBINARY format as its protocol. And, if the protocol is changed, the content type of the request message should be changed as well according to the message's actual protocol.

Lint check for type mismatch of ComponentFactory/ViewModelFactory

  • If type Foo's companion object extends ComponentFactory or ViewModelFactory, its type parameter must be Foo.

I'd like to have a lint checker for the above rule.
That is, the lint checker reports mismatches like this:

class Foo {
    // snip...
    companion object : ComponentFactory<Bar>() { // <- WRONG!!
        override fun createComponent(context: Context): Bar = TODO()
    }
}

[Bug] Enable DexGuard will causing exception `Service implementation is not found`

Enabling DexGuard which causing exceptions on the lich component. Please see stack traces below.

Stack Traces

04-22 08:50:50.240 25666 25666 E AndroidRuntime: Caused by:  com.linecorp.lich.component.FactoryDelegationException: Service implementation is not found.
04-22 08:50:50.240 25666 25666 E AndroidRuntime: 	at com.linecorp.lich.component.ComponentFactory.loadServiceLoaderComponent(Unknown Source:40)
04-22 08:50:50.240 25666 25666 E AndroidRuntime: 	at o.uhc$a.b(Unknown Source:20)
04-22 08:50:50.240 25666 25666 E AndroidRuntime: 	at o.uhc$a.createComponent(Unknown Source:10)

To fix the issue we need to add these rules which can be used for Proguard or Dexguard

-keep public class <class>.DefaultComponentProvider { *; }
-keep interface <class>.ComponentProvider { *; }
-keep public class * implements <class>.ServiceLoaderComponent { *; }
-if public class **.*$Companion extends <class>.ComponentFactory
-keep class <1>.<2> { *; }

[Improvement] resumeWith and resumeWithException needs to be cooperative

Background

It's a best practice to be cooperative to check the state of cancellable on suspendCancellableCoroutine.

Especially on this resumeWith and resumeWithException which manifested on OkHttpExtensions.kt.

The rationale of having cooperative implementation basically to make sure the invocation does not throw an exception such as CompletionHandlerException on the call-site.

Proposal of changes

from

suspend fun <T> OkHttpClient.call(request: Request, responseHandler: (Response) -> T): T =
    suspendCancellableCoroutine { cont ->
        val call = newCall(request)
        cont.invokeOnCancellation { call.cancel() }
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                val result = runCatching { response.use(responseHandler) }
                cont.resumeWith(result)
            }

            override fun onFailure(call: Call, e: IOException) {
                cont.resumeWithException(e)
            }
        })
    }

to

suspend fun <T> OkHttpClient.call(request: Request, responseHandler: (Response) -> T): T =
    suspendCancellableCoroutine { cont ->
        val call = newCall(request)
        cont.invokeOnCancellation { call.cancel() }
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                if (cont.isActive) {
                    val result = runCatching { response.use(responseHandler) }
                    cont.resumeWith(result)
                }
             }

            override fun onFailure(call: Call, e: IOException) {
               if (cont.isActive) {
                   cont.resumeWithException(e)
               }
            }
        })
    }

[New Feature][Component] Support lifecycle-aware component

Hello, I have an idea for lich/component. The following is the proposal for the idea.

Summary

This proposal is to suggest to add some new API for lich component. The new APIs will release the component object when given lifecycle takes ON_DESTROY event, and then it will not be used anymore.

Proposal

Background

In now, the component created by lich is considered as singleton because ComponentFactory<T> is defined by object keyword. Also this implementation is guided in README.md. But this will imply some problems.

  • Even if the component is not used anymore, it will not be collected by the GC. This will hold a lot of memory to hold the components. For example, components for log-in Activity, will not be created again if user logged in.
  • context.getComponent has some confused definition. This function call looks that the created component will receive the given context instance. But the component will receive the applicationContext.

Thanksfully, there is a concept of Lifecycle and ViewModelStore.clear() in Android framework. I hope that the lifetime of the component is the same as the lifecycle of context.

Sketched change area

I don't have the concrete implementation for this proposal. So this section is just a sketch to implement this proposal. After this proposal is approved, the concrete implementation should be followed.

The steps to implement this proposal is followings.

  1. Change the interface of ComponentProvider
  2. Implement new observers for each lifecycles
  3. Implement DefaultComponentProvider.getComponent to observe lifecycle.
  4. Add some new APIs to support new features.

Change of interface

To get the two types of owners, the ComponentProvider is needed to be changed.

interface ComponentProvider {
    fun <T : Any> getComponent(context: Context, factory: ComponentFactory<T>, owner: Any?) : T
}

New ViewModel or LifecycleObserver for releasing component

New classes for releasing the component are needed.

  • LifecycleObserver for releasing with the lifecycle
  • ViewModel for releasing with the viewmodel lifecycle
// A [ViewModel] for releasing components
// The components will be released with the [ViewModelStore.clear] call.
internal class ResetComponentViewModel(
    private val factory: ComponentFactory<*>
): ViewModel() {
    override fun onCleared() {
        super.onCleared()
        ComponentFactory.Accessor.setComponent(factory, null)
    }

    internal class Factory(
        private val factory: ComponentFactory<*>
    ): ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return ResetComponentViewModel(factory) as T
        }
    }
}

// A [LifecycleObserver] for releasing components
// The components will be released with [ON_DESTROY] event.
internal class ResetComponentLifecycleObserver(
    private val factory: ComponentFactory<*>
): DefaultLifecycleObserver {
    override fun onDestroy(owner: LifecycleOwner) {
        ComponentFactory.Accessor.setComponent(factory, null)
    }
}

Implement ComponentProvider.getComponent

ComponentProvider should observe the given lifecycle using the context.

internal class DefaultComponentProvider : ComponentProvider {
    override fun <T : Any> getComponent(context: Context, factory: ComponentFactory<T>, owner: Any?): T {
        val accessor = ComponentFactory.Accessor
        accessor.getComponent(factory)?.let {
            @Suppress("UNCHECKED_CAST")
            return if (it is Creating) it.await() else it as T
        }

        val creating = Creating()
        while (!accessor.compareAndSetComponent(factory, null, creating)) {
            accessor.getComponent(factory)?.let {
                @Suppress("UNCHECKED_CAST")
                return if (it is Creating) it.await() else it as T
            }
        }

        // Pass the given context using lifecycle
        val componentContext = when(owner) {
            is ViewModelStoreOwner -> context.applicationContext
            is LifecycleOwner -> context
            else -> context.applicationContext
        }
        val result = runCatching { accessor.createComponent(factory, componentContext) }
        creating.setResult(result)

        accessor.setComponent(factory, result.getOrNull())

        // observe lifecycle
        when(owner) {
            is ViewModelStoreOwner -> {
                val viewModelFactory = ComponentFactory.ResetComponentViewModel.Factory(factory)
                ViewModelProvider(owner, viewModelFactory)
                    .get<ComponentFactory.ResetComponentViewModel>()
            }
            is LifecycleOwner -> {
                owner.lifecycle.addObserver(
                    ComponentFactory.ResetComponentLifecycleObserver(factory)
                )
            }
        }

        return result.getOrThrow()
    }
}

Add new APIs

From the Components.kt, the entry point of this new features should be provided.

@JvmName("get")
fun <T : Any> Context.getComponent(factory: ComponentFactory<T>): T = when(this) {
    // component is bounded to the lifecycle of ViewModel
    is ViewModelStoreOwner -> componentProvider.getComponent(applicationContext, factory, this)

    // component is bounded to the lifecycle of the android component
    is LifecycleOwner -> componentProvider.getComponent(this, factory, this)

    // component is singleton
    else -> componentProvider.getComponent(applicationContext, factory, null)
}

// This API will be used for the ViewModelProvider.Factory. 
// The extras[APPLICATION_KEY] is application object so there is no way to determine whether the given context is bounded to lifecylce.
// So a new API will be needed to support components in ViewModel.
@JvmName("get")
fun <T: Any> CreationExtras.getComponent(factory: ComponentFactory<T>): T {
    val context = requireNotNull(this[APPLICATION_KEY])
    val viewModelStoreOwner = requireNotNull(this[VIEW_MODEL_STORE_OWNER_KEY])

    return componentProvider.getComponent(context, factory, viewModelStoreOwner)
}

Affected impact

If an application is using Lich-component, there is no things to change immediately. The public APIs are not changed, so it is okay to use lich-component, if there is no need to optimize memory. But:

  • if you want to optimize memory, then you must change the API calls in ViewModelProvider.Factory to use CreationExtras.getComponent to handle the lifecycle of components in ViewModel.
  • or if your component has some mutable state, then you must change the context.getComponent calls to context.applicationContext.getComponent manually.

Remained problems of above change

The remained problems of the above change are considered as followings.

  • If a component is used in multiple lifecycle-aware Android components at once, then context object might be leaked. The developers should use applicationContext for multiple lifecycle-aware Android components.

If you have an idea, please let me know.

Kotlin 1.3.70 fails to compile savedState.required()

class FooViewModel(savedState: SavedState) : AbstractViewModel() {
    private val fooArg: String by savedState.required()
}

In Kotlin 1.3.70, compilation for the above code fails with an exception like this:

e: java.lang.IllegalStateException: Couldn't obtain compiled function body for 
public final inline operator fun provideDelegate(thisRef: kotlin.Any?, 
property: kotlin.reflect.KProperty<*>): com.linecorp.lich.viewmodel.InitializedSavedState<T> 
defined in com.linecorp.lich.viewmodel.RequiringSavedStateDelegate[DeserializedSimpleFunctionDescriptor@72dd0f7d]

This seems to be due to a bug where the provideDelegate function does not work well with inline classes.

https://kotlinlang.org/docs/reference/delegated-properties.html#providing-a-delegate-since-11
https://kotlinlang.org/docs/reference/inline-classes.html

Fix the workarounds for dependency problems of lifecycle-viewmodel(-ktx).

androidx.fragment:fragment-ktx:1.4.0 depends on androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1, and
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0 depends on androidx.lifecycle:lifecycle-viewmodel:2.4.0.
But, lifecycle-viewmodel-ktx:2.3.1 and lifecycle-viewmodel:2.4.0 contain some classes with the same names, which leads to compilation errors.

Therefore, as a workaround, we explicitly added dependencies to androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0.

IDE cannot access generated sources from lich savedstate compiler

Related issue : google/ksp#746

When using ksp and savedstate compiler, IDE(such as Android Studio) cannot resolve symbols from generated sources as default.

image

In this case, many IDE feature will be malfunction such as Code Formatting

In other libraries cases such as Room which are providing annotation processors, library provides Builder API which are wrapping generated sources and allow to access.

ex : https://developer.android.com/training/data-storage/room#usage

Similar to Room, is it possible to avoid referencing the generated code directly?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.