Git Product home page Git Product logo

mavericks's Introduction

Build Status Maven Central codecov.io

Mavericks (formerly MvRx): Android on Autopilot

For full documentation, check out our docs site.

Mavericks is the Android framework from Airbnb that we use for nearly all product development at Airbnb.

When we began creating Mavericks, our goal was not to create yet another architecture pattern for Airbnb, it was to make building products easier, faster, and more fun. All of our decisions have built on that. We believe that for Mavericks to be successful, it must be effective for building everything from the simplest of screens to the most complex in our app.

This is what it looks like:

data class HelloWorldState(val title: String = "Hello World") : MavericksState

/**
 * Refer to the wiki for how to set up your base ViewModel.
 */
class HelloWorldViewModel(initialState: HelloWorldState) : MavericksViewModel<HelloWorldState>(initialState) {
    fun getMoreExcited() = setState { copy(title = "$title!") }
}

class HelloWorldFragment : Fragment(R.layout.hello_world_fragment), MavericksView {
    private val viewModel: HelloWorldViewModel by fragmentViewModel()

    override fun invalidate() = withState(viewModel) { state ->
        // Update your views with the latest state here.
        // This will get called any time your state changes and the viewLifecycleOwner is STARTED.
    }
}

Installation

Gradle is the only supported build configuration, so just add the dependency to your project build.gradle file:

dependencies {
  implementation 'com.airbnb.android:mavericks:x.y.z'
}

The latest version of mavericks is Maven Central

For full documentation, check out the docs site

Legacy documentation for MvRx 1.x can still be found in the wiki

mavericks's People

Contributors

adammc331 avatar alisonthemonster avatar allenchen1154 avatar apramana avatar benschwab avatar bernaferrari avatar bmarty avatar chrisbanes avatar digitalbuddha avatar eboudrant avatar elihart avatar gpeal avatar haroldadmin avatar hellohuanlin avatar inktomi avatar itsandreramon avatar jakoss avatar jpventura avatar kenyee avatar marukami avatar mishrabhilash avatar mmcwong avatar rossbacher avatar sanogueralorenzo avatar sav007 avatar subhrajyotisen avatar tasomaniac avatar wiryadev avatar ykc415 avatar zacsweers 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  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

mavericks's Issues

Conductor and MvRx

Hey, Just wondering how crazy it is for me to attempt integratin MvRx with Conductor. I would be more than happy to provide a PR/separate module, but I first need to know how feasible this is.

Is MvRx based on delegation? Haven't check the internals yet to make sure of this nor I've fully tested MvRx myself, but if its delegation based I should be able to use MvRx on Controllers (or any sort of Views) instead of Fragments, right?

Any tips are very much appreciated :)

Thanks!

execute() sets subscribeOn() scheduler

Looking at execute(), it seems to set the subscribeOn() scheduler to be the single threaded background subscriber the VM keeps. While I can see why the observeOn() scheduler is set, I don't see why you force the actual subscription on the same scheduler.

All of my observables are coming from a database, so I want them on the io() scheduler ideally.

Some class from sample app should be in the library

While testing MvRx it happened I did a lot of code copy/paste from the sample app. Mainly theses classes : MvRxEpoxyController, BaseFragment (minus the androidx.navigation part), MvRxViewModel. I think it would really help to have theses class in the library. Especially the epoxy related classes. May be in an optional library when we combine MvRx with Epoxy. To add on this, some of the class from sample app are referenced in the doc, it is a bit confusing to not see them in the main library (ex: https://github.com/airbnb/MvRx/wiki/Advanced-Concepts#mvrxviewmodel). Thanks!

findViewById in models

In todomvrx and sampleyou use findViewById in models. Is there any reason behind this additional mapping instead of importing views directly with Kotlin?

Instead of writing this one:

@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class Header @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val titleView by lazy { findViewById<TextView>(R.id.title) }

    init {
        inflate(context, R.layout.header, this)
    }

    @TextProp
    fun setTitle(title: CharSequence?) {
        titleView.text = title
    }
}

write this one (assuming that I have R.id.titleView instead of R.id.title)

@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class Header @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    init {
        inflate(context, R.layout.header, this)
    }

    @TextProp
    fun setTitle(title: CharSequence?) {
        titleView.text = title
    }
}

New logo/icon proposal

Good day sir. I am a graphic designer and i am interested in designing a logo for your good project. I will be doing it as a gift for free. I just need your permission first before I begin my design. Hoping for your positive feedback. Thanks

Decouple State and Args

Now to pass some arguments to ViewModel we have to include them into the initialState, like in the example below.

data class MyState(val foo: Int) : MvRxState {
    constructor(args: MyArgs) : this(args.foo)
}

It works well in the simple examples, but sometimes we need some info that does not need to be rendered. An api parameter, some userId, for example. We have to use "dead" fields in state that make no sense for the View and only needed to construct a ViewModel or perform background operations.

@Parcelize
data class MyArgs(val userId: String)

data class MyState(val args: MyArgs, val coinsCount: Int = 0) : MvRxState{
    constructor(args: MyArgs) : this(args)
}

class MyViewModel(initialState: MyState) : BaseMvRxViewModel(initialState) {
    init {
        // retrieve `userId`
       initialState.args.let { 
             // use it 
       }
    }
}

The factory approach is not working, because MvRxViewModelFactory<MyState, MyViewModel>#create gets the internally constructed state.

Wouldn't be nice to have something like other MvRxViewModelArgFactory version, taking MyArgs in create?

class MyViewModel(private val args: MyArgs, initialState: MyState) : BaseMvRxViewModel(initialState) {
    init {
       // just use the args
       args.let { // ... }
    }
    companion object : MvRxViewModelArgFactory<MyState, MyViewModel> {
        @JvmStatic
        override fun create(activity: FragmentActivity, args: MyArgs): MyViewModel {
            return MyViewModel(args, createState())
        }
    }
}

Any thoughts? It would be a neat thing to hide this implementation details from the View

One-off events

In google samples certain events are propagated through SingleLiveEvent.

While this concept goes against MVI principle, it's needed in certain cases to greatly reduce boilerplate. For example, to show toast in current implementation I have to create a property inside my state, and inside View send an event back to ViewModel telling it that toast was shown.

Another similar solution can be used in every ViewModel to fire such one-off events.

Second example can be easily done through an additional subject inside MvRxStateStore and subscription inside withState

ViewModelFactory error uses wrong class name

viewModelClass.kotlin.primaryConstructor?.parameters?.get(0)?.type != state::class -> {
                    "${MvRxViewModelFactory::class.java.simpleName} must have primary constructor with a " +
                        "single parameter that takes initial state of ${state::class.java.simpleName}. Found type " +
                        "${viewModelClass.kotlin.primaryConstructor?.parameters?.get(0)?.type?.javaClass?.simpleName}"
                }

this is using the viewmodelfactory class in the error message instead of the view model class

How stable is MvRx at the moment, how much will API change?

I'm building average size (~40 screens) greenfield app now and I need to deliver it quickly. I like the idea of MvRx, with Epoxy it saves so much boilerplate. Thank you for your work!
Version 0.5 scares me a bit. How stable is MvRx at the moment and how much will API change in the future?

MvRx at Airbnb utilities

Hi,

Looking through the wiki, more specifically "MvRx at Airbnb" page, I found some things that look really useful and convenient, namely the Fragments/MvRxFragments class for easy registering and creating fragments (even without a need for creating our own activity) with optional cross-module access, showFragment/showModal for fragment navigation and finally MvRxLauncher for starting fragments from launcher shortcuts. Is there any possibility for open sourcing those functionalities and sharing them with the community?

I understand that perhaps this is out of scope for the core MvRx, but maybe it could be released as an extension module?

The best way to implement forms with MvRx & Epoxy

Could you tell me how I could improve my pattern for creating forms with MvRx?
What's the best way to fix EditText recycling ? Keeping all EditTexts in single view like in your ToDo sample solves the problem, but it's less scalable solution, isn't it?

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <android.support.design.widget.TextInputLayout
        android:id="@+id/inputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.design.widget.TextInputEditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </android.support.design.widget.TextInputLayout>
</merge>

My Model is below.
setTextAndCursor is Extension fuction inspired by @elihart code airbnb/epoxy#426)
onChange is Extension function wrapping TextWatcher

@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class Input @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    init {
        inflate(context, R.layout.input, this)
    }

    @TextProp
    fun setText(email: CharSequence) {
        editText.setTextAndCursor(email)
    }

    @CallbackProp
    fun setOnChange(onChange: ((String) -> Unit)?) {
        editText.onChange { onChange?.invoke(it) }
    }

    @ModelProp
    fun setError(error: String?) {
        inputLayout.error = error
    }
}

Code in Fragment

    override fun epoxyController() = simpleController(viewModel) { state ->
       
        with(state.registerForm) {

            input {
                id("email")
                text(email)
                error(emailError)
                onChange { viewModel.onEmailChange(it) }
            }

            input {
                id("firstName")
                text(firstName)
                error(firstNameError)
                onChange { viewModel.onFirstNameChange(it) }
            }

Code in ViewModel

    fun onEmailChange(email: String) {
        setState { copy(registerForm = registerForm.copy(email = email, emailError = emailError(email))) }
    }

    fun onFirstNameChange(firstName: String) {
        setState { copy(registerForm = registerForm.copy(firstName = firstName, firstNameError = firstNameError(firstName))) }
    }
  1. Could you tell me if my pattern is right?
  2. What's the best way to solve the problem with recycling EditText

Avoid rebind views without Epoxy.

In the wiki, it said

Views should be considered ephemeral. invalidate() will be called every time state changes. As a result, you should use something like epoxy or optimize your view's setters to no-op if the same data is set again.

I can understand that in Android there are no mechanism for avoid rebind the same date to a view and prevent re-draw UI.
But except epoxy ,is there any recommended way for doing that? I don't really want to re-layout my screens in an RecyclerView.

Another problem is that, as I can imagine. When the business logic /screen become more complex. The MvRxState can have tons of parameters. Won't it be a problem to manage?

Unrestrict subscribe?

As it currently seems to be the case, only Fragments have been implemented as MvRxView. It certainly does make sense for apps that have minimal number of Activitys and large number of Fragments. I'm working on an app, it has a lot of Activitys, some of them do not even include any Fragment. To be able to use the Activitys as MvRxViews, with little effort, I managed to implement MvRxView for them. I followed the exact same way it was for Fragments. I haven't done extensive testing but it works.

However, the problem in this is that I'm using a function that is restricted to the library package. Would it be ok if it was made public?

Paging Lib + MvRx

Hello ๐Ÿ‘‹

What would be the recommended (or suggested) way to use the Android Paging Library with MvRx?

Should put as property of the state the DataFactory, PagedList, LivePagedList?

I would like to hear your thoughts on this.

Thanks and keep up the great work

Work with Dagger

At the moment I see no way of integrating with an existing ViewModelProvider.Factory. This is necessary to be able to use Dagger to inject ViewModels.

Example fragment

Is there a way?

Subscriptions need to happen in `onCreate`.

When a fragment is added to the backstack, and then resumed, almost all lifecycles besides onCreate will be called again. If you call subscribe or selectSubscribe in a different lifecycle, such as onViewCreated or onActivityCreated everytime you resume your fragment you will create a new subscription, which is almost certainly not intended.

We need to:

  1. Update MvRx documents to state that all subscriptions should be made in onCreate.
  2. Update MvRx demos to follow this rule.

http://vinsol.com/blog/wp-content/uploads/2014/08/fragment_lifecycle.png

What is the role of MvRxLifecycleAwareObserver in MvRx?

I see this issuse #15, but I don't understand the specific reason. In the absence of MvRxLifecycleAwareObserver, if the state changes, the direct subscribe stateStore.observable should also deliver the new state. In my opinion, the role of MvRxLifecycleAwareObserver is in order to deliver the state after the Fragment/Activity is restarted, instead of delivering the state every time the stateStore.observable changes, reduce the unnecessary invalidate. If my understand is wroing, please correct me. Thank you.

DrawerLayout + generally more examples wanted

Hello

I'm trying to set up a DrawerLayout in the MvRx sample project but i am kind of stuck.

Totally new to both epoxy and MvRx in general and i have been playing around for a week now learning about the framework and testing components i need to convert our app to MvRx.

What i am wondering is how you setup a DrawerLayout view using MvRx.

Also the current examples are very comprehensive for noobs. Anyone has a minimalist starter available?

// JQ

Avoid delivering state's last value when unlocked

In the BaseMvRxViewModel , I found that all the subscribe method finally call subscribeLifecycle, in which MvRxLifecycleAwareObserver 's property alwaysDeliverLastValueWhenUnlocked is set to true. So when the phone's screen is on and off, the same state value diliver again(the Toast show again). Is there anyway to avoid this?

Wrong emit order (Loading after Success)

I've noticed a bit of weirdness since moving to MvRx. After debugging it today, it turns out that the emitting order using execute() is wrong for Observables which emit immediately:

D  imageProviderObservable reducer: Success(value=app.tivi.tmdb.TmdbImageUrlProvider@696bcd)
D  imageProviderObservable reducer: com.airbnb.mvrx.Loading@7749791c

imageProviderObservable in this instance is a BehaviorSubject with a default value, so will always emit immediately on subscribe. If the Observable does not emit again, the final state is Loading which is obviously wrong.

Google maps and BottomNavigationView

Hello

I'm trying to setup google maps inside of BottomNavigationView tab using MvRx.

I have to questions:

  1. What is the best pracice for setting up BottomNavigationView in MvRx?
  2. Using the code below I managed to load the map once but the second time i open the tab i get the following error. Therefore i am wondering what the recommended way to setup google maps in a BottomNavigationView fragment using MvRx is?
Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f080089, tag null, or parent id 0xffffffff with another fragment for com.google.android.gms.maps.SupportMapFragment

My fragment looks like this:

class MapFragment : BaseFragment() {

    private val mViewModel: ChatViewModel by fragmentViewModel()

    override fun epoxyController() = simpleController(mViewModel) {state ->
        mapView{
            id("map")
        }
    }
}

And view:

@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT)
class MapView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    init {
        inflate(context, R.layout.fragment_map, this)
    }

}

Map fragment (Crashes on second inflate)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

main_activity

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


        <fragment
            android:id="@+id/my_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            app:navGraph="@navigation/nav_graph" />

        <android.support.design.widget.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:background="@android:color/white"
            app:itemTextColor="@color/bottom_navigation_text"
            app:layout_behavior="android.support.design.behavior.HideBottomViewOnScrollBehavior"
            app:menu="@menu/bottom_navigation_main" />


</android.support.design.widget.CoordinatorLayout>

MainActivity

class MainActivity: BaseMvRxActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setupNavigation()
    }

    private fun setupNavigation() {
        val navController = findNavController(R.id.my_nav_host_fragment)
        bottom_navigation.setupWithNavController(navController)
    }

    override fun onSupportNavigateUp() =
            findNavController(R.id.my_nav_host_fragment).navigateUp()

}

MvRx xml layout files not rendering in android studio

In the example repo from AirBnBs new framework MvRx xml layout files does not render in the android studio editor as shown in the screenshot below. Neither can I make the views render in Android Studio for my own projects

enter image description here

full_screen_message.xml

    <?xml version="1.0" encoding="utf-8"?>
    <merge
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:parentTag="android.widget.FrameLayout"
        >
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_gravity="center"
            android:gravity="center_horizontal">
            <ImageView
                android:id="@+id/icon"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:scaleType="centerCrop"
                android:contentDescription="@string/asd" />
    
            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="@string/asdasdasdad"/>
    
        </LinearLayout>
    </merge>

FullScreenMessageView.kt:

    package com.airbnb.mvrx.todomvrx.views
    
    import android.content.Context
    import android.support.annotation.DrawableRes
    import android.util.AttributeSet
    import android.widget.FrameLayout
    import android.widget.ImageView
    import android.widget.TextView
    import com.airbnb.epoxy.ModelProp
    import com.airbnb.epoxy.ModelView
    import com.airbnb.epoxy.TextProp
    import com.airbnb.mvrx.todomvrx.todoapp.R
    
    @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT)
    class FullScreenMessageView @JvmOverloads constructor(
            context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : FrameLayout(context, attrs, defStyleAttr) {
    
        private val iconView by lazy { findViewById<ImageView>(R.id.icon) }
        private val titleView by lazy { findViewById<TextView>(R.id.title) }
    
        init {
            inflate(context, R.layout.full_screen_message, this)
        }
    
        @ModelProp
        fun setIconRes(@DrawableRes drawableRes: Int) {
            iconView.setImageResource(drawableRes)
        }
    
        @TextProp
        fun setTitle(title: CharSequence) {
            titleView.text = title
        }
    }

Crash in MvRxViewModelStore.saveViewModels

This was on a debug build so not Proguard related.

(FYI TiviMvRxFragment is a copy of MvRxFragment with a different super type)

Log:

2018-09-03 15:16:39.445 11000-11000/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.tivi.debug, PID: 11000
    java.lang.IllegalStateException: Resource not found in classpath: kotlin/kotlin.kotlin_builtins
        at kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl.createBuiltInPackageFragmentProvider(BuiltInsLoaderImpl.kt:64)
        at kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl.createPackageFragmentProvider(BuiltInsLoaderImpl.kt:42)
        at kotlin.reflect.jvm.internal.impl.builtins.KotlinBuiltIns.createBuiltInsModule(KotlinBuiltIns.java:148)
        at kotlin.reflect.jvm.internal.impl.platform.JvmBuiltIns.<init>(JvmBuiltIns.kt:56)
        at kotlin.reflect.jvm.internal.impl.platform.JvmBuiltIns.<init>(JvmBuiltIns.kt:31)
        at kotlin.reflect.jvm.internal.components.RuntimeModuleData$Companion.create(RuntimeModuleData.kt:54)
        at kotlin.reflect.jvm.internal.ModuleByClassLoaderKt.getOrCreateModule(moduleByClassLoader.kt:58)
        at kotlin.reflect.jvm.internal.KDeclarationContainerImpl$Data$moduleData$2.invoke(KDeclarationContainerImpl.kt:35)
        at kotlin.reflect.jvm.internal.KDeclarationContainerImpl$Data$moduleData$2.invoke(KDeclarationContainerImpl.kt:32)
        at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93)
        at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
        at kotlin.reflect.jvm.internal.KDeclarationContainerImpl$Data.getModuleData(Unknown Source:7)
        at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:46)
        at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:43)
        at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93)
        at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
        at kotlin.reflect.jvm.internal.KClassImpl$Data.getDescriptor(Unknown Source:7)
        at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:172)
        at kotlin.reflect.jvm.internal.KClassImpl.getMemberScope$kotlin_reflection(KClassImpl.kt:178)
        at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:152)
        at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:43)
        at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93)
        at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
        at kotlin.reflect.jvm.internal.KClassImpl$Data.getDeclaredNonStaticMembers(Unknown Source:8)
        at kotlin.reflect.full.KClasses.getDeclaredMemberProperties(KClasses.kt:163)
        at com.airbnb.mvrx.PersistStateKt.persistState(PersistState.kt:36)
        at com.airbnb.mvrx.PersistStateKt.persistState$default(PersistState.kt:34)
        at com.airbnb.mvrx.MvRxViewModelStore$saveViewModels$3$1.invoke(MvRxViewModelStore.kt:53)
        at com.airbnb.mvrx.MvRxViewModelStore$saveViewModels$3$1.invoke(MvRxViewModelStore.kt:20)
        at com.airbnb.mvrx.StateContainerKt.withState(StateContainer.kt:6)
        at com.airbnb.mvrx.MvRxViewModelStore.saveViewModels(MvRxViewModelStore.kt:52)
        at com.airbnb.mvrx.MvRxViewModelStore.saveViewModels(MvRxViewModelStore.kt:43)
        at app.tivi.util.TiviMvRxFragment.onSaveInstanceState(TiviMvRxFragment.kt:34)
        at android.support.v4.app.Fragment.performSaveInstanceState(Fragment.java:2629)
        at android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.java:2910)
        at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:2971)
        at android.support.v4.app.FragmentController.saveAllState(FragmentController.java:134)
        at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:591)
        at android.support.v7.app.AppCompatActivity.onSaveInstanceState(AppCompatActivity.java:510)
        at android.app.Activity.performSaveInstanceState(Activity.java:1549)
        at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1443)
        at android.app.ActivityThread.callActivityOnSaveInstanceState(ActivityThread.java:4809)
2018-09-03 15:16:39.446 11000-11000/? E/AndroidRuntime:     at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:4157)
        at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:4121)
        at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4196)
        at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:41)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

invalidate is called twice in the first invalidation

when setState is called for the very first time, invalidate of the MvRxView (fragment) is called twice.
It might also be realated to the fact that invalidate is not called for a fresh Fragment until setState is called. (i would expect at least 1 invalidate after initialization)
The code below triggers a setState when user clicks on the text view. This is the log I receive:

08-23 21:14:03.900 8309-8338/com.birbit.android.devto D/MVRX: setting state to update 1535084043900
08-23 21:14:03.900 8309-8309/com.birbit.android.devto D/MVRX: updating UI to update 1535084043900
08-23 21:14:03.901 8309-8309/com.birbit.android.devto D/MVRX: updating UI to update 1535084043900
data class MyState(val text : String = "bar") : MvRxState
class MainViewModel(initialState : MyState) : BaseMvRxViewModel<MyState>(initialState, false) {
    fun updateValue() {
        setState {
            val newText = "update ${System.currentTimeMillis()}"
            Log.d("MVRX", "setting state to $newText")
            copy(text = newText)
        }
    }
}

class MainFragment : BaseMvRxFragment() {
    private val viewModel by fragmentViewModel(MainViewModel::class)
    override fun invalidate() {
        withState(viewModel) {
            Log.d("MVRX", "updating UI to ${it.text}")
            main_content.text = it.text
        }
    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        main_content.setOnClickListener {
            viewModel.updateValue()
        }
    }
}

iOS version

According to this airbnb/epoxy#562 (comment) Epoxy for iOS is coming soon, are you planning to open source iOS MvRX as well?
Would be great to have consistent architecture on both platforms.

Thank you for your awesome work!

Proguard rules

I've noticed lots of reflection using in MvRx, and indeed my app crashes with Proguard. MvRx should publish some recommended Proguard rules.

MvRxSwift

Are there plans for a Swift version of MvRx?

MvRxStateStore based on kotlin coroutines

Since MvRxStateStore can be a performance bottleneck on screens with state changing very frequently, maybe it would be beneficial to implement it with coroutines instead of Rx?
I can provide performance comparisons to determine actual performance gain in case it's needed

Where do dialogs/snackbars/etc fit into MvRx?

I'd be curious to know how transient ui elements like dialogs/snackbars/etc fit into the overall MvRx pattern (if at all). Is it something like a flag on the state? Or is that outside of the scope of what MvRx is managing?

Google maps fragment in epoxy view

I'm currently exploring the new MvRx stack from Airbnb

Rigth now i am adding a google maps fragment successfully to a BaseMvRxFragment in a BottomNavigationView.

The problem is that the 2. time i navigate to the MapFragment my app crashes and i get the following error message:

    E/AndroidRuntime: FATAL EXCEPTION: main
        Process: getflareapp.com.s2s, PID: 19184
        android.view.InflateException: Binary XML file line #7: Binary XML file line #7: Error inflating class fragment
        Caused by: android.view.InflateException: Binary XML file line #7: Error inflating class fragment
        Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f080093, tag null, or parent id 0xffffffff with another fragment for com.google.android.gms.maps.SupportMapFragment
            at android.support.v4.app.FragmentManagerImpl.onCreateView(FragmentManager.java:3752)
            at android.support.v4.app.FragmentController.onCreateView(FragmentController.java:120)
            at android.support.v4.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:405)
            at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:387)
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:780)
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
            at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
            at android.view.View.inflate(View.java:23239)
            at getflareapp.com.s2s.views.MapView.<init>(MapView.kt:19)
            at getflareapp.com.s2s.views.MapView.<init>(MapView.kt:14)
            at getflareapp.com.s2s.views.MapView.<init>(Unknown Source:6)
            at getflareapp.com.s2s.views.MapViewModel_.buildView(MapViewModel_.java:42)
            at getflareapp.com.s2s.views.MapViewModel_.buildView(MapViewModel_.java:22)

MapFragment.kt

    /**
     * A simple [BaseFragment] subclass.
     *
     */
    class MapFragment : BaseFragment() {
    
    
        private val mViewModel: ChatViewModel by fragmentViewModel()
    
        override fun epoxyController() = simpleController(mViewModel) {state ->
    
    
            mapView{
                id("map")
            }
        }
    }

MapView.kt

 @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT)
    class MapView @JvmOverloads constructor(
            context: Context,
            attrs: AttributeSet? = null,
            defStyleAttr: Int = 0
    ) : FrameLayout(context, attrs, defStyleAttr) {
    
        init {
            // TODO: Fix crash on second view.
            inflate(context, R.layout.view_map, this)
        }
    }

view_map.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/map2"
            android:name="getflareapp.com.s2s.ui.map.MapFragment"
            class="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".ui.MainActivity" />
    
    </FrameLayout>

Thanks for any help! <3

wrong exception when view model is missing the constructor

When i forget to have a constructor w/o state in my ViewModel, it receives an index out of bounds exception instead of reporting the real problem.

The problem seems to be in MvRxViewModelProvider#createFactoryViewModel which assumes the primary constructor has parameters.

require(!primaryConstructor.parameters[0].isOptional) { "initialState may not be an optional constructor parameter." }

View Model:

data class MyState(val text : String = "bar") : MvRxState
class MainViewModel : BaseMvRxViewModel<MyState>(
    initialState = MyState(text = "blah"),
    debugMode = false
)

Fragment:

private val viewModel by fragmentViewModel(MainViewModel::class)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.subscribe {
               //....
        }
    }

exception:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.birbit.android.devto/com.birbit.android.devto.MainActivity}: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at java.util.ArrayList.get(ArrayList.java:437)
        at com.airbnb.mvrx.MvRxViewModelProvider.createDefaultViewModel(MvRxViewModelProvider.kt:68)
        at com.airbnb.mvrx.MvRxViewModelProvider$get$factory$1.invoke(MvRxViewModelProvider.kt:47)
        at com.airbnb.mvrx.MvRxViewModelProvider$get$factory$1.invoke(MvRxViewModelProvider.kt:15)
        at com.airbnb.mvrx.MvRxFactory.create(MvRxFactory.kt:7)
        at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:134)
        at com.airbnb.mvrx.MvRxViewModelProvider.get(MvRxViewModelProvider.kt:52)
        at com.birbit.android.devto.MainFragment$$special$$inlined$fragmentViewModel$1.invoke(MvRxExtensions.kt:207)
        at com.birbit.android.devto.MainFragment$$special$$inlined$fragmentViewModel$1.invoke(Unknown Source:0)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.birbit.android.devto.MainFragment.getViewModel(Unknown Source:25)
        at com.birbit.android.devto.MainFragment.onViewCreated(MainFragment.kt:42)

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.