Dmitri
Dmitri

Reputation: 2982

Implementing ViewModelProvider.Factory fails with Inheritance from an interface with '@JvmDefault' after adding Android Jetpack Compose navigation lib

I had this simple viewmodel provider factory code (borrowed from one of Google's code samples), which happily obliged and compiled perfectly...

fun <VM : ViewModel> viewModelProviderFactoryOf(
    create: () -> VM
): ViewModelProvider.Factory = SimpleFactory(create)


private class SimpleFactory<VM : ViewModel>(
    private val create: () -> VM
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val vm = create()
        if (modelClass.isInstance(vm)) {
            @Suppress("UNCHECKED_CAST")
            return vm as T
        }
        throw IllegalArgumentException("Can not create ViewModel for class: $modelClass")
    }
}

... Until i introduced this library:

implementation "androidx.navigation:navigation-compose:2.5.0-rc02"

Now all of a sudden compilation fails with:

Inheritance from an interface with '@JvmDefault' members is only allowed with -Xjvm-default option

(error pointing to : ViewModelProvider.Factory )

Why? What did navigation bring in with it? (I did confirm 100% it's the navigation lib causing it, remove it, and the error is gone)

Note: Q is not about how to solve it, compiler suggests it clearly, adding these args - freeCompilerArgs += "-Xjvm-default=all". The Q is about why this is happening.

Upvotes: 5

Views: 2388

Answers (4)

PavelZh
PavelZh

Reputation: 104

this code worked for me

val viewModel: ViewModel by viewModels(
        ownerProducer = { requireParentFragment() }, 
        factoryProducer = { VMFactory }
)

Upvotes: 0

Hossein Farrokhi
Hossein Farrokhi

Reputation: 87

first, upgrade all of your fragment and activity dependencies to 1.5.0 and your lifecycle dependencies to 2.5.0. by doing so, your activities and fragments can handle the creationExtras part without requiring you to do anything. then in your factory method, replace this line:

//replace this line
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
//with this one
    override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {

and you are good to go. source: These extras are provided automatically by your Activity or Fragment when using Activity 1.5.0 and Fragment 1.5.0, respectively.

Upvotes: 0

vaudevillain
vaudevillain

Reputation: 31

We encountered this also when we added compose libraries. We solved it by adding the xjvm-default option to our gradle file.

android {
  kotlinOptions {
    freeCompilerArgs +=  "-Xjvm-default=all"
  }
}

Upvotes: 3

Dmitri
Dmitri

Reputation: 2982

OK, found it. The problem is that bringing in compose navigation, version 2.5.0-rc also updates androidx.lifecycle to 2.5.0-rc (from, in my case, 2.3.0), and in there, they changed the Factory interface by adding a method with implementation (and also adding implementation to the existing method in the interface).

Compare:

Factory implementation before 2.5.0:

public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

Note one method, create, without implementation.

Here is the rewrite they did in 2.5.0

public interface Factory {
        /**
         * Creates a new instance of the given `Class`.
         *
         * Default implementation throws [UnsupportedOperationException].
         *
         * @param modelClass a `Class` whose instance is requested
         * @return a newly created ViewModel
         */
        public fun <T : ViewModel> create(modelClass: Class<T>): T {
            throw UnsupportedOperationException(
                "Factory.create(String) is unsupported.  This Factory requires " +
                    "`CreationExtras` to be passed into `create` method."
            )
        }

        /**
         * Creates a new instance of the given `Class`.
         *
         * @param modelClass a `Class` whose instance is requested
         * @param extras an additional information for this creation request
         * @return a newly created ViewModel
         */
        public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
            create(modelClass)

       
          ...
        }

So that explains it, this interface is now a default implementation interface, and to inherit from it, need to add the compiler args, as suggested by the compiler (freeCompilerArgs += "-Xjvm-default=all").

Upvotes: 9

Related Questions