WHOATEMYNOODLES
WHOATEMYNOODLES

Reputation: 2731

Hilt - Java How to runtime inject String from Fragment to ViewModel constructor?

I have a String inside my Fragment that I'm trying to inject into the Fragment's ViewModel.

I've been following this tutorial https://github.com/google/dagger/issues/2287

but I can't get the correct syntax from kotlin to java to work.

@AssistedFactory
public interface MainViewModelFactory {
    MainViewModel create(String s);
}
@HiltViewModel
public class MainViewModel extends ViewModel {
    private static final String TAG = "MainViewModel";

    @Inject
    public MainViewModel(@Assisted String testString) {
        Log.d(TAG, "MainViewModel: Success injecting: " + testString);
    }

    @SuppressWarnings("unchecked")
    public static ViewModelProvider.Factory provideFactory(
            MainViewModelFactory assistedFactory,
            String s
    ){
        return new ViewModelProvider.Factory() {
            @NonNull
            @Override
            public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
                return (T) assistedFactory.create(s);
            }
        };
    }
}
    @Inject
    MainViewModelFactory viewModelFactory;

    private MainViewModel mainViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mainViewModel = new ViewModelProvider(this).get(MainViewModel.provideFactory(viewModelFactory, "Test string"));

        //This line fails to even compile because I can't figure out how to fix my syntax from the kotlin to java conversion.
        
    }

enter image description here

How can I correctly provide the ViewModel from my factory method?

When I try to compile it I get

An assisted factory's abstract method must return a type with an @AssistedInject-annotated constructor. on my factory class's method create.

Edit:

I know you can use a module to provide the string, but I'm using it as a simple example before I try to get it working with injecting an object.

Using Hilt 2.37:

implementation 'com.google.dagger:hilt-android:2.37'
annotationProcessor 'com.google.dagger:hilt-compiler:2.37'

Upvotes: 2

Views: 1758

Answers (2)

WHOATEMYNOODLES
WHOATEMYNOODLES

Reputation: 2731

Took two days to figure it out...but here's how to runtime inject (assisted injection) with hilt using Java.

Requirements: Dagger 2.31+

Important notes:

DO NOT annotate your ViewModel with @HiltViewModel or it will throw a compile time exception which did not help me identify the issue at all.

 ViewModel constructor should be annotated with @Inject instead of @AssistedInject.
  [Hilt] Processing did not complete. See error above for details.

Full code with String example:

@AssistedFactory
public interface MainViewModelFactory {
    MainViewModel create(String s);
}
public class MainViewModel extends ViewModel {
    private static final String TAG = "MainViewModel";

    @AssistedInject
    public MainViewModel(@Assisted String testString) {
        Log.d(TAG, "MainViewModel: Success injecting: " + testString);
    }

    @SuppressWarnings("unchecked")
    public static ViewModelProvider.Factory provideFactory(
            MainViewModelFactory assistedFactory,
            String s
    ){
        return new ViewModelProvider.Factory() {
            @NonNull
            @Override
            public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
                return (T) assistedFactory.create(s);
            }
        };
    }
}
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    @Inject
    MainViewModelFactory viewModelFactory;

    private MainViewModel mainViewModel;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mainViewModel = new ViewModelProvider(
                this, 
                MainViewModel.provideFactory(viewModelFactory, "Test string") 
        ).get(MainViewModel.class);

}

Took me a while to figure out there was a second param for ViewModelProvider which needed the injected ViewModelFactory to create the ViewModelProvider

Hopefully this answer helps people in the future. I couldn't find any examples of how to do it.

Helpful answers:

https://github.com/google/dagger/issues/2287#issuecomment-761811164

Converting the kotlin way of creating the Java equivalent of ViewModelProviders was quite confusing...

Upvotes: 3

OneDev
OneDev

Reputation: 617

First, You must add @Inject Annotation On Constructor Of Your Class
Second, if your Constructor of your class has an argument or Object that is hard to Instantiate for dagger, You have to provide it from Module Class. for Example if your class use String Class or use an interface, abstract class that cannot be instantiate, you must provide an instance of that class in your Module Class.
In your ViewModel Class you can get it like repository class, because repository class is a simple class, dagger knows how to instantiate it, so there is no problem.

public class FragmentViewModel extends ViewModel {

    private final RemoteRepository remoteRepository;
    private final TestClass testClass;

    @Inject
    public CardInfoViewModel(RemoteRepository remoteRepository, TestClass testClass) { //How to inject object from Fragment into constructor?
        this.remoteRepository = remoteRepository;
        this.testClass = testClass;
    }
}

module class

@InstallIn(Singletone.class)
@Module
public class ModuleClass {

    @Singletone
    @Provide
    public TestClass provideTestClass() {
        return new TestClass("Some Text");
    }
}

Upvotes: 0

Related Questions