Starting a kotlin viewModel from java code - java

I want to init and call a kotlin viewModel from a java class.
this is my viewModel
#HiltViewModel
class PermProdsTestViewModel #Inject constructor(
private val prodsUseCase: ProductUseCase
) : ViewModel() {
private val _prods = MutableStateFlow(ProdsState())
val prods: StateFlow<ProdsState> = _prods
fun getPermittedProducts(serviceName: String?, productTypes: List<String>?, permission: String?, subServiceName: String?, filter: Boolean?) =
viewModelScope.launch(Dispatchers.IO) {
permittedProdsUseCase.invoke(serviceName, productTypes, permission, subServiceName, filter).collect() {
when (it) {
is DataResult.Success -> {
_prods.value = ProdsState(products = it.data)
Timber.d("Api request success, getting results")
}
is DataResult.Error -> {
ProdsState(error = it.cause.localizedMessage ?: "Unexpected Error")
Timber.d("Error getting permitted products")
}
}
}
}}
and I want to call it from a java file activity and use the method.
How can i do it?

Did you maybe forgot to put annotation #AndroidEntryPoint above your class declaration? I tried to access to your ViewModel class from java class and it works for me.
Here is my code:
#AndroidEntryPoint
public class TestActivity extends AppCompatActivity {
private PermProdsTestViewModel vm;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
vm = new ViewModelProvider(this).get(PermProdsTestViewModel.class);
vm.getPermittedProducts();
}
}

Related

cannot find implementation for RoomDatabase while injecting ViewModel into Fragment with Factory

I am struggling with viewmodel injection. I have been following tutorials and changed the code a little bit in order to adjust it to my needs, but the app crashes.
I have App class holding my DaggerComponent with it's modules. Inside it's onCreate I have:
component = DaggerAppComponent.builder().daoModule(DaoModule(this)).build()
My AppModule:
#Singleton
#Component(modules = [DaoModule::class, ViewModelModule::class])
interface AppComponent {
val factory: ViewModelFactory
}
ViewModelModule :
#Module
abstract class ViewModelModule {
#Binds
#Singleton
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#Singleton
#IntoMap
#ViewModelKey(TaskViewModel::class)
abstract fun splashViewModel(viewModel: TaskViewModel): ViewModel
}
MyFactory:
#Singleton
class ViewModelFactory #Inject constructor(
private val viewModels: MutableMap<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
viewModels[modelClass]?.get() as T
}
I used here ViewModelKey, ViewModelModule and Factory, and Fragment extension function to perform Fragment viewmodel injection. I found it online and used it succesfuly on previous projects. This is my util function:
#MainThread
inline fun <reified VM : ViewModel> Fragment.daggerViewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this }
) = createViewModelLazy(
VM::class,
{ ownerProducer().viewModelStore },
{ App.component.factory }
)
And my DaoModule.
#Module
class DaoModule(private val app: Application) {
#Provides
#Singleton
fun getDB(): TaskDatabase = TaskDatabase.getAppDatabase(context())
#Provides
#Singleton
fun context(): Context = app.applicationContext
#Provides
fun gettaskDao(taskDatabase: TaskDatabase) : TaskDao = taskDatabase.TaskDao()
}
My entity:
#Entity(tableName = "userinfo")
data class Task(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") val id: Int = 0,
#ColumnInfo(name = "name") val name: String,
#ColumnInfo(name = "email") val email: String,
#ColumnInfo(name = "phone") val phone: String?
)
My TaskDatabase as follows:
#Database(entities = [Task::class], version = 1)
abstract class TaskDatabase : RoomDatabase() {
abstract fun TaskDao(): TaskDao
companion object {
private var INSTANCE: TaskDatabase? = null
fun getAppDatabase(context: Context): TaskDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext, TaskDatabase::class.java, "AppDBB"
)
.allowMainThreadQueries()
.build()
}
return INSTANCE!!
}
}
}
My Dao interface.
#Dao
interface TaskDao {
#Query("SELECT * FROM userinfo")
fun getAllTaskInfo(): List<Task>?
#Insert
fun insertTask(user: Task?)
#Delete
fun deleteTask(user: Task?)
#Update
fun updateTask(user: Task?)
}
And now I have a logic to init my TaskViewModel inside my Fragment and attach observer to my Task List. However the app crashes.
Inside my fragment I have:
val viewModel: TaskViewModel by daggerViewModels { requireActivity() }
and also:
DaggerFragmentComponent
.builder()
.appComponent((requireActivity().application as App).getAppComponent())
.build()
.inject(this)
viewModel.allTaskList.observe(viewLifecycleOwner) {
// textView.text = it.toString()
}
and my TaskViewModel class is as follows:
class TaskViewModel #Inject constructor(var taskDao: TaskDao) : ViewModel() {
private var _allTaskList = MutableLiveData<List<Task>>()
val allTaskList = _allTaskList as LiveData<List<Task>>
init {
getAllRecords()
}
private fun getAllRecords() = _allTaskList.postValue(taskDao.getAllTaskInfo())
fun insertTask(task: Task) {
taskDao.insertTask(task)
getAllRecords()
}
}
Now I understand that this is A LOT of code, but can somebody help me figure this out? The dagger sees it's graph as I can build the project, so all the dependencies are provided. What I did wrong here? My logcat:
I found the solution myself. This was missing.
implementation 'androidx.room:room-runtime:2.5.0-alpha01'

Unable to instantiate ViewModel in Android

I am trying to achieve MVVM design pattern in my application.I have created viewmodel and repository class but when I am trying to instantiate viewmodel in my MainActivity its showing error red line below MainActivity at the time of instantiation in below line.
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
Below is my code:
MainActivity.java
public class MainActivity extends AppCompatActivity {
PdfViewModel pdfViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
}
}
PdfViewModel.java
public class PdfViewModel extends AndroidViewModel {
private PdfRepository pdfRepository;
public PdfViewModel(#NonNull Application application) {
super(application);
pdfRepository = new PdfRepository(application);
}
public LiveData<List<Pdfs>> getAllPdfs(){
return pdfRepository.getMutableLiveData();
}
}
PdfRepository.java
public class PdfRepository {
private ArrayList<Pdfs> list = new ArrayList<>();
private MutableLiveData<List<Pdfs>> mutableLiveData = new MutableLiveData<>();
private Application application;
public PdfRepository(Application application){
this.application = application;
}
public MutableLiveData<List<Pdfs>> getMutableLiveData(){
SharedPreferences preferences = application.getSharedPreferences("Credentials", Context.MODE_PRIVATE);
String email = preferences.getString("email",null);
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<Pdfs>> call = apiService.getFiles(email);
call.enqueue(new Callback<List<Pdfs>>() {
#Override
public void onResponse(Call<List<Pdfs>> call, Response<List<Pdfs>> response) {
if(response.body() != null){
list = (ArrayList<Pdfs>) response.body();
mutableLiveData.setValue(list);
}
}
#Override
public void onFailure(Call<List<Pdfs>> call, Throwable t) {
TastyToast.makeText(application,t.getMessage(),TastyToast.LENGTH_SHORT,TastyToast.ERROR).show();
}
});
return mutableLiveData;
}
}
What needs to be corrected in the above code?
Your code is trying to create a new instance of the class ViewModelProvider (with the new keyword) and that's not the right way to instantiate a ViewModel.
On MainActivity, instead of:
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
try:
pdfViewModel = ViewModelProviders.of(this).get(PdfViewModel.class);
Notice the right class is ViewModelProviders (with an "s" at the end) and you need to call the static method of instead of creating a new instance of it with new. If you can't import that class, make sure you have the dependency 'androidx.lifecycle:lifecycle-extensions:2.2.0' added to app/build.gradle.
To make your code even clearer, I'd suggest learning about the Kotlin KTX method viewModels, as described here. You'd need to use Kotlin for that though.

Can't access variable from main project?

I cannot access the hi variable from my library class. Why? Check it out:
I have this inteface in my library:
interface ContextAccessor {
fun getApplicationContext(): Application?
}
And this code as well:
class SomeLibraryClass {
private var mContextAccessor: ContextAccessor?
String extractedHi = null
fun setContextAccessor(contextAccessor: ContextAccessor?) {
mContextAccessor = contextAccessor
}
fun someOtherMethod() {
mContextAccessor?.getAppContext()?.let { nonNullContext ->
// use nonNullContext here
extractedHi = nonNullContext.hi; // i get error here!
}
}
}
And this class in my project:
public class MyActivity extends Activity implements MyActivity.ContextAccessor {
private SomeLibraryClass someLibraryClassInstance = SomeLibraryClass();
public String hi = "hi";
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ContextAccessor reference is set to some library class
someLibraryClassInstance.setContextAccessor(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
// Super important!
someLibraryClassInstance.setContextAccessor(null);
// OR create some method like `someLibraryClassInstance.removeContextAccessor(this)`
}
#Override
public Application getApplicationContext() {
return super.getApplication();
}
}
Add hi property to the ContextAccessor interface:
interface ContextAccessor {
val hi: String
// ...
}
And in MyActivity implement getHi() method:
#NotNull
#Override
public String getHi() {
return hi;
}
In your library class SomeLibraryClass you can access to it like the following:
var extractedHi: String? = null
fun someOtherMethod() {
extractedHi = mContextAccessor?.hi
}

How to update server response in UI using MVVM pattern

I am using MVVM pattern in my app I have separate repository class for network operations. In repository class I am getting response from the server. How can I show Toast message send from the server in my main activity.
Below is my code:
Repository.java
public class MyRepository {
MutableLiveData<List<Facts>> mutableLiveData = new MutableLiveData<>();
Application application;
public MyRepository(Application application) {
this.application = application;
}
public MutableLiveData<List<Facts>> getMutableLiveData(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
apiService.getFacts().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Facts>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Facts> facts) {
if(facts.size() > 0 && facts != null){
mutableLiveData.setValue(facts);
}
}
#Override
public void onError(Throwable e) {
TastyToast.makeText(application,e.getMessage(),TastyToast.LENGTH_SHORT,
TastyToast.ERROR).show();
}
#Override
public void onComplete() {
}
});
return mutableLiveData;
}
}
FactsViewModel.java
public class FactsViewModel extends AndroidViewModel {
MyRepository repo;
public FactsViewModel(#NonNull Application application) {
super(application);
repo = new MyRepository(application);
}
public LiveData<List<Facts>> getAllFacts(){
return repo.getMutableLiveData();
}
}
MainActivity.java
private void myFacts(){
FactsViewModel viewModel = new ViewModelProvider(this).get(FactsViewModel.class);
viewModel.getAllFacts().observe(this, new Observer<List<Facts>>() {
#Override
public void onChanged(List<Facts> facts) {
adapter = new FactsAdapter(facts,getActivity());
recycle.setAdapter(adapter);
}
});
}
How can I show error toast messages in MainActivity?
To implement that you firstly need to create a class which has the status of the response ,
Loading which is before the fetching of the data and there you can set progress bar to visible then on success you would set the data to your adapter and right after your hide your progress bar and in the on failure one , you show the toast message error
This is the generic class
class AuthResource<T>(
var authStatus : AuthStatus? = null,
var data : T,
var msg : String? = null
)
fun <T> success(#Nullable data: T): AuthResource<T> {
return AuthResource(
AuthStatus.Success,
data,
null
)
}
fun <T> Error(#NonNull msg: String?, #Nullable data: T) : AuthResource<T>? {
return AuthResource(
AuthStatus.ERROR,
data,
msg
)
}
fun <T> loading(#Nullable data: T): AuthResource<T>? {
return AuthResource(
AuthStatus.LOADING,
data,
null
)
}
enum class AuthStatus {
Success, ERROR, LOADING
}
This is my view model where i implement the authResource with the api response
class MainViewModel #Inject constructor( private var webAuth: WebAuth,
private var favFoodDao: FavFoodDao,
private var application: Application) : ViewModel() {
/// you have to create MediatorLiveData with authresource which contains your modelclass
private var mediatorLiveData = MediatorLiveData<AuthResource<WrapLatestMeals>>()
///Here you return a livedata object
fun ObserverCountries(): LiveData<AuthResource<WrapCountries>> {
var liveData = LiveDataReactiveStreams.fromPublisher(
webAuth.getCountries()
///onerrorreturn , rxjava operator which returns error in case
///of response failure
.onErrorReturn(object : Function<Throwable, WrapCountries> {
override fun apply(t: Throwable): WrapCountries {
var country = WrapCountries()
return country
}
})
.map(object : Function<WrapCountries,
AuthResource<WrapCountries>> {
override fun apply(t: WrapCountries):
AuthResource<WrapCountries> {
if(t.meals.isNullOrEmpty())
{
return Error(
"Error",
t
)!!
}
return success(t)
}
})
.subscribeOn(Schedulers.io())
)
//add that data to mediatorLivedata
mediatorLiveDataCountries.addSource(liveData, Observer {
mediatorLiveDataCountries.postValue(it)
mediatorLiveDataCountries.removeSource(liveData)
})
return mediatorLiveDataCountries
}
This is how you handle the status in your MainActivity
mainViewModel = ViewModelProvider(this,provider)[MainViewModel::class.java]
mainViewModel.ObserverCountries().observe(viewLifecycleOwner, Observer {
when(it.authStatus) {
AuthStatus.LOADING -> /// here you show progressbar in response pre-fetch
{
countriesFragmentBinding.countryprogress.show()
}
AuthStatus.Success -> { // here you update your ui
countriesAdapter = CountriesAdapter(it.data.meals!!,
requireContext())
countriesFragmentBinding.recyclercountries.adapter = countriesAdapter
countriesAdapter!!.deleteCategory(23)
countriesFragmentBinding.countryprogress.hide()
}
AuthStatus.ERROR -> // here you hide your progressbar and show your toast
{
countriesFragmentBinding.countryprogress.hide()
ToastyError(requireContext(),getString(R.string.errorretreivingdata))
}
}
})
return countriesFragmentBinding.root
}
}

How to provide objects of the same type? Dagger2

I am new at Dagger2 and tried to build such sample to understood how does it work.
There is my sample code :
MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
protected ApiInterface apiInterface;
#Inject
protected Integer valueInt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
App.getComponent().inject(this);
}
public void testButton(View view) {
if (apiInterface == null || valueInt == null) {
Log.e("TAG", "apiInterface == null");
} else {
Log.e("TAG", "apiInterface != null : " + apiInterface.value + " : " + valueInt);
}
}
}
Component
#Singleton
#Component(modules = {ModelModule.class, AnotherModule.class})
interface AppComponent {
void inject(MainActivity mainActivity);
}
Module
#Module
class ModelModule {
#Provides
int provideInt() {
return 1;
}
#Provides
ApiInterface provideApiInterface(int i) {
return ApiModule.getApiInterface(i);
}
}
Module
#Module
class AnotherModule {
#Provides
Integer getInt(){
return 3;
}
}
as you can see in my sample in MainActivity i inject Integer
#Inject
protected Integer valueInt;
and also i want to use int to provide value as argument for this method provideApiInterface(int i).
And eventually i get such error
Error:(11, 10) error: java.lang.Integer is bound multiple times:
#Provides int com.krokosha.aleksey.daggertwo.ModelModule.provideInt()
#Provides Integer com.krokosha.aleksey.daggertwo.AnotherModule.getInt()
What am i doing wrong?
How am i supposed to provide this argument in proper way to avoid such error?
You need to use qualifier annotations
#Module
class ModelModule {
#Provides
#Named("FirstInt")
int provideInt() {
return 1;
}
}
#Module
class AnotherModule {
#Provides
#Named("SecondInt")
int provideInt() {
return 1;
}
}
and use this qualifiers when injecting dependecies
#Inject
protected ApiInterface apiInterface;
#Inject
#Named("FirstInt") //or whatever you need
protected int valueInt;
Hope it helps!
Also check official docs - http://google.github.io/dagger/
Kotlin example of providing 2 instances of the same class type (works the same with primitive types, too) using the #Name annotation.
PrefsModule.kt
#Module
object PrefsModule {
private const val packageName = "com.example.app"
const val ENCRYPTED_PREFS = "$packageName.ENCRYPTED_PREFS"
const val PREFS = "$packageName.PREFS"
#Singleton
#Provides
#Named(ENCRYPTED_PREFS)
#JvmStatic
fun provideEncryptedSharedPreferences(application: Application): Prefs =
Prefs(
ENCRYPTED_PREFS,
application.applicationContext,
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
#Singleton
#Provides
#Named(PREFS)
#JvmStatic
fun provideUnencryptedSharedPreferences(application: Application): Prefs =
Prefs(PREFS, application.applicationContext)
}
Field Injection:
#Inject
#Named(PrefsModule.ENCRYPTED_PREFS)
lateinit var ePrefs: Prefs
#Inject
#Named(PrefsModule.PREFS)
lateinit var prefs: Prefs
Call the variables after you call inject() in say, your Activity's onCreate() or where ever.
For those curious about what the Prefs class looks like: stackoverflow.com

Categories