I am trying to use dagger 2 to inject dependencies in a fragment in my application
I have the following modules
Network Module
#Module(includes = ContextModule.class)
public class NetworkModule {
#Provides
Cache getCacheFile(Context context) {
File cacheFile = new File(context.getCacheDir(), "okhttp-cache");
return new Cache(cacheFile, 10 * 1000 * 1000);
}
#Provides
HttpLoggingInterceptor getHttpLoggingInteceptor() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
return logging;
}
#Provides
OkHttp3Downloader getOkHttp3Downloader(Context context) {
return new OkHttp3Downloader(context);
}
#Provides
OkHttpClient getOkHttpClient(HttpLoggingInterceptor interceptor, Cache cache) {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newBuilder().cache(cache)
.addInterceptor(interceptor)
.build();
return okHttpClient;
}
#Provides
Retrofit getRetrofit(OkHttpClient client, GsonConverterFactory gsonConverterFactory, RxJava2CallAdapterFactory callAdapter) {
return new Retrofit.Builder()
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(callAdapter)
.baseUrl("https://api.themoviedb.org/3/")
.client(client)
.build();
}
#Provides
GsonConverterFactory getGsonConverterFactory() {
return GsonConverterFactory.create();
}
#Provides
RxJava2CallAdapterFactory getRxJavaFactory() {
return RxJava2CallAdapterFactory.create();
}
}
ActivityBuilder Module
#Module
public abstract class ActivityBuilder {
#ContributesAndroidInjector
abstract NewsListActivity bindMoviesListActivity();
#ContributesAndroidInjector(modules = NewsListFragmentModule.class)
abstract NewsListFragment bindNewsListFragment();
}
Here is the AppComponent
#Component(modules = {AndroidSupportInjectionModule.class, ContextModule.class, ActivityBuilder.class})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
AppComponent build();
}
void inject(NewsApp app);
}
This is my activity class where the fragment is embedded
class NewsListActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, HasSupportFragmentInjector {
#Inject
lateinit var mAndroidInjector: AndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news_list)
setSupportActionBar(toolbar)
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.frame_main, NewsListFragment(), "news-list-fragment")
.commit()
}
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return mAndroidInjector
}
}
And finally this is my Fragment class
class NewsListFragment : Fragment() {
lateinit var picasso: Picasso
#Inject
lateinit var newsService: NewsService
#Inject
lateinit var newsFragmentViewModel: NewsFragmentViewModel
lateinit var newsListAdapter: NewsListAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_news_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
swipe_layout.setColorSchemeColors(resources.getColor(R.color.orange),resources.getColor(R.color.green),resources.getColor(R.color.blue))
newsListAdapter = NewsListAdapter(newsItemClickListener, picasso)
val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
rv_news_top_headlines.layoutManager = layoutManager
rv_news_top_headlines.setHasFixedSize(true)
rv_news_top_headlines.adapter = newsListAdapter
newsFragmentViewModel = ViewModelProviders.of(this).get(NewsFragmentViewModel::class.java)
swipe_layout.isRefreshing = true
newsFragmentViewModel.init(newsService)
loadDataFromApi()
swipe_layout.setOnRefreshListener {
loadDataFromApi()
}
}
private fun loadDataFromApi() {
// Load data from API
}
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
private val newsItemClickListener = fun(article: Articles) {
toast(article.title.toString())
}
}
I followed many different tutorials to find out how to use the new AndroidInjection in Fragments so I don't have the links of those tutorials?
Is there anything I'm doing wrong?
The error
e: /Users/sriramr/Desktop/android/NewsApp/app/src/main/java/in/sriram/newsapp/di/AppComponent.java:12: error: [dagger.android.AndroidInjector.inject(T)] dagger.android.AndroidInjector<android.support.v4.app.Fragment> cannot be provided without an #Provides- or #Produces-annotated method.
public interface AppComponent {
^
dagger.android.AndroidInjector<android.support.v4.app.Fragment> is injected at
in.sriram.newsapp.ui.newslist.NewsListActivity.mAndroidInjector
in.sriram.newsapp.ui.newslist.NewsListActivity is injected at
dagger.android.AndroidInjector.inject(arg0)
Try to change following in NewsListActivity
AndroidInjector<Fragment>
to:
DispatchingAndroidInjector<Fragment>
Also You haven't posted your application class. Make sure it has following things:
class NewsApp : Application(), HasActivityInjector {
#Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
// some other initialization
}
fun activityInjector(): AndroidInjector<Activity>? {
return activityDispatchingAndroidInjector
}
}
Related
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();
}
}
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'
I have a Client class (written in Kotlin in an Android app) that implements an interface ReadyCallback (written in Java in a library of the app, the app is dependent on this library). In Client I have a createClient() method which will create a client with the parameter of ReadyCallback. If it's ready, I will perform other tasks by calling classC.otherMethod(), if not ready, I just create the client without doing other stuff:
In the library:
// Somewhere in this library, I have logic to call `readyCallback.onReady()` when I consider it's "ready"
interface ReadyCallback {
void onReady()
}
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady();
}
}
}
In the app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { onReady() }
}
override fun onReady() {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
In unit test, I want to verify that when I create the client and it's ready, classC's otherMethod() will be invoked. I tried to do the following but it's not correct:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
#Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
#Test
fun `when create client then call otherMethod`() {
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
}
The error message shows:
Wanted but not invoked:
classC.otherMethod();
Actually, there were zero interactions with this mock.
I think the reason I got this error is because, if I don't call getReadyCallback(), it means I am not invoking the callback, so there's no call to classC.otherMethod(). But other than that I am really stuck on this, I don't know how to unit test my desire behavior (If it's ready, classC.otherMethod() will be called, if not ready, this method won't be called).
I know I can't do things like below because unitUnderTest is not a mock object:
callbackMock = mock()
whenever(unitUnderTest.getReadyCallback()).thenReturn(callbackMock)
whenever(clientProviderMock.create(callbackMock).thenReturn(client)
Can anyone help me out please?
The only way I can think of is to add a boolean flag in callback's onReady() method. So it will become:
In library:
interface ReadyCallback {
void onReady(final boolean isReady)
}
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady(true);
} else {
readyCallback.onReady(false);
}
}
}
In app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { isReady -> onReady(isReady) }
}
override fun onReady(isReady: Boolean) {
if (isReady) {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
}
In unit test:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
#Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
#Test
fun `when create client and ready then call otherMethod`() {
unitUnderTest.onReady(true)
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
#Test
fun `when create client and not ready then do not call otherMethod`() {
unitUnderTest.onReady(false)
unitUnderTest.createClient()
verifyZeroInteractions(classCMock)
}
}
But I still don't know how to test without the boolean parameter in the callback's method. Does anyone know how to do that?
I think I figured it out. I don't need a parameter in onReady().
In library:
interface ReadyCallback {
void onReady()
}
// place to determine when is "ready"
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady();
}
}
}
In app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { onReady() }
}
override fun onReady() {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
In unit test:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
#Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
#Test
fun `when create client and ready then call otherMethod`() {
unitUnderTest.onReady()
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
#Test
fun `when create client and not ready then do not call otherMethod`() {
unitUnderTest.createClient()
verifyZeroInteractions(classCMock)
}
}
searched on many sites and in various articles, but without effect, so I ask for your help with Dagger 2 (DI)
I can not understand what the problem is
I'm new to Dagger 2 (Di) and I'm just studying it, please help me understand
My code:
MainActivity.kt
class MainActivity : AppCompatActivity() {
#Inject
lateinit var networkService: NetworkService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
App().gerAppComponents().inject(mainActivity = this)
setContentView(R.layout.activity_main)
networkService.buildService(service = Movies::class.java).getPopularMovie(apiKey = "test", language = "en-US", page = 1)
}
}
App.kt
class App : Application() {
fun gerAppComponents(): AppComponent {
return DaggerAppComponent.builder().appNetworkModule(AppNetworkModule())
.build()
}
}
ServiceBuild.kt
object ServiceBuild : NetworkService {
private val client = OkHttpClient.Builder().build()
private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://api.themoviedb.org/3")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
override fun <T> buildService(service: Class<T>): T = retrofit.create(service)
}
NetworkService.kt
interface NetworkService {
fun <T> buildService(service: Class<T>): T
}
AppNetworkModule.kt
#Module
class AppNetworkModule {
#Provides
#Singleton
fun getRetrofitService(): ServiceBuild {
return ServiceBuild
}
}
AppComponent.kt
#Singleton
#Component(modules = arrayOf(AppNetworkModule::class))
interface AppComponent {
fun inject(mainActivity: MainActivity)
}
Build error
app/build/tmp/kapt3/stubs/debug/com/rebus/client/di/components/AppComponent.java:10: error: [Dagger/MissingBinding] com.rebus.client.services.impl.NetworkService cannot be provided without an #Provides-annotated method.
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
com.rebus.client.services.impl.NetworkService is injected at
com.rebus.client.MainActivity.networkService
com.rebus.client.MainActivity is injected at
com.rebus.client.di.components.AppComponent.inject(com.rebus.client.MainActivity)
Change your code ServiceBuild to NetworkService like this:
#Module
class AppNetworkModule {
#Provides
#Singleton
fun getRetrofitService(): NetworkService {
return ServiceBuild
}
}
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