fragment cannot be provided without an #Provides-annotated method. Kotlin - kotlin

My Application class
class MyApp : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
/*#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>*/
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder().application(this).build().inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> =
activityInjector
//override fun supportFragmentInjector(): AndroidSupportInjection<Fragment>
= fragmentInjector
}
App Component
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class,
AppModule::class, BuilderModule::class))
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApp)
}
App Module
#Module
class AppModule {
#Provides
#Singleton
fun provideUtil(application: Application): Utils = Utils(application)
}
Builder Module
#Module
abstract class BuilderModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
#ContributesAndroidInjector
abstract fun contributeHomeFragment(): HomeFragment
}
Main Activity
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun supportFragmentInjector(): AndroidInjector<Fragment> =
fragmentInjector
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this); // Call before super!
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(HomeFragment())
}
fun replaceFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frameContainer, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
Home Fragment
class HomeFragment : Fragment() {
#Inject
lateinit var utils: Utils
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this) // Providing the dependency
super.onAttach(context)
}
override fun onCreateView(inflater: LayoutInflater, container:
ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
utils.toaster("Injected")
}
I am getting this error
D:\Workspace\DaggerKotlin\app\build\tmp\kapt3\stubs\debug\com\cvsingh\daggerkotlin\di\AppComponent.java:8: error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent {
^
java.util.Map>> is injected at
dagger.android.DispatchingAndroidInjector.(…, injectorFactoriesWithStringKeys)
dagger.android.DispatchingAndroidInjector is injected at
com.cvsingh.daggerkotlin.ui.MainActivity.fragmentInjector
com.cvsingh.daggerkotlin.ui.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.cvsingh.daggerkotlin.di.AppComponent ? com.cvsingh.daggerkotlin.di.BuilderModule_ContributeMainActivity.MainActivitySubcomponent
}

You're doing it wrong with your MyApp class, you should use DaggerApplication to simplify you dagger integration :
class MyApp : DaggerApplication() {
private val applicationInjector = DaggerApplicationComponent.builder()
.application(this)
.build()
override fun applicationInjector() = applicationInjector
}
And for your information you can simplify the same way your activites and fragments using DaggerAppCompatActivity and DaggerFragment
for example your activity could be :
class MainActivity : DaggerAppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(HomeFragment())
}
fun replaceFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frameContainer, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
If you don't want to use DaggerApplication, DaggerAppCompatActivity, etc.. (sometimes you need to extends from another class) then simply copy paste code from DaggerApplication, DaggerAppCompatActivity, etc in your class

Related

Can't log any data from API call

I'm pretty new to kotlin, and I feel pretty much overwhelmed by it. I'd like to ask - how I can display any data from MutableLiveData? I've tried to Log it, but it doesn't seem to work. I've already added the internet permission to the manifest. Here's the code:
ApiServices
interface ApiServices {
#GET("/fixer/latest/")
fun getRatesData(
#Query("base") base: String,
#Query("apikey") apikey: String
): Call<CurrencyModel>
companion object {
private const val url = "https://api.apilayer.com/"
var apiServices: ApiServices? = null
fun getInstance(): ApiServices {
if (apiServices == null) {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
apiServices = retrofit.create(ApiServices::class.java)
}
return apiServices!!
}
}
}
Repository
class CurrencyRepository constructor(private val apiServices: ApiServices) {
fun getLatestRates() = apiServices.getRatesData("EUR", "API_KEY");
}
ViewModel
class CurrencyViewModel constructor(private val currencyRepository: CurrencyRepository) :
ViewModel() {
val currencyRatesList = MutableLiveData<CurrencyModel>()
val errorMessage = MutableLiveData<String>()
fun getLatestRates() {
val response = currencyRepository.getLatestRates();
response.enqueue(object : retrofit2.Callback<CurrencyModel> {
override fun onResponse(
call: retrofit2.Call<CurrencyModel>,
response: Response<CurrencyModel>
) {
currencyRatesList.postValue(response.body())
}
override fun onFailure(call: retrofit2.Call<CurrencyModel>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
FactoryViewModel
class CurrencyViewModelFactory constructor(private val repository: CurrencyRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(CurrencyViewModel::class.java)) {
CurrencyViewModel(this.repository) as T
}else{
throw IllegalArgumentException("Couldn't found ViewModel")
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private val retrofitService = ApiServices.getInstance()
lateinit var viewModel: CurrencyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this, CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
.get(CurrencyViewModel::class.java)
viewModel.currencyRatesList.observe(this, Observer {
Log.d(TAG, "onCreate: $it")
})
viewModel.errorMessage.observe(this, Observer {
viewModel.getLatestRates()
})
}
}
You never call viewModel.getLatestRates() in your onCreate() to fetch an initial value for your LiveData, so it never emits anything to observe. The only place you have called it is inside your error listener, which won't be called until a fetch returns an error.
Side note, I recommend naming the function "fetchLatestRates()". By convention, "get" implies that the function returns what it's getting immediately rather than passing it to a LiveData later when its ready.
And a tip. Instead of this:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: CurrencyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
viewModel = ViewModelProvider(this, CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
.get(CurrencyViewModel::class.java)
//...
}
}
You can do this for the same result:
class MainActivity : AppCompatActivity() {
val viewModel: CurrencyViewModel by viewModels(CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
}
}

What is the correct way to set a TAG constant for debugging in Kotlin?

Here are a couple of functionally similar code blocks...
class MainActivity : AppCompatActivity() {
companion object{
const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate Called")
}
...
}
and
const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate Called")
}
...
}
Should the const val TAG = "MainActivity" be in a companion object, or top level? Would top level become an issue with namespaces, like other classes declaring top level const val TAG = "OtherClass"?

My RecyclerView in ViewModel crashing app - kotlin android studio

I have problem with RecyclerView into ViewModel.
RecyclerView without viewmodel working perfect.
My MainFragment:
private lateinit var shoppingListViewModel: ShoppingListViewModel
private lateinit var categoryAdapter: CategoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shoppingListViewModel = ViewModelProvider(requireActivity())[ShoppingListViewModel::class.java]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.categoryRV.layoutManager = LinearLayoutManager(requireContext())
shoppingListViewModel.allCategories.observe(viewLifecycleOwner, { <-- when I add this line,
updateCategories(it) crashing my app
})
}
private fun updateCategories(list: List<Category>) {
categoryAdapter = CategoryAdapter(list)
binding.categoryRV.adapter = categoryAdapter
}
My ViewModel:
class ShoppingListViewModel(application: Application) : AndroidViewModel(application) {
private val repository = Repository(application)
val allCategories = repository.showAllCategories()
}
My Repo:
class Repository(app: Application) {
private val shoppingListsDao = ShoppingListDatabaseBuilder.getInstance(app.applicationContext).shoppingListDao()
fun showAllCategories(): LiveData<List<Category>> {
return shoppingListsDao.showAllCategories()
}
}
My Interface Dao:
#Dao
interface ShoppingListDao {
#Query("SELECT * FROM category")
fun showAllCategories(): LiveData<List<Category>>
}
Everything looks good, no errors. I don't know what going on :(
I find my problem.
I need to update of version DB :)

Flow with Room in Kotlin

i am new to kotlin android i am trying to use room with flow my code as follows
my entity as follows
#Entity(tableName = "user_data")
data class User (
#PrimaryKey(autoGenerate = true)
val id:Int,
val firstName:String,
val lastName:String,
val age:Int)
my database class as follows
#Database(entities = [User::class],version = 1,exportSchema = false)
abstract class UserDatabase :RoomDatabase(){
abstract fun userDao():UserDao
companion object{
#Volatile
private var INSTANCE:UserDatabase?=null
fun getDataBaseInstance(context:Context):UserDatabase
{
var tempInstance= INSTANCE
if(tempInstance!=null)
{
return tempInstance
}
synchronized(this)
{
val instance=Room.databaseBuilder(context.applicationContext,UserDatabase::class.java,"user_database")
.build()
INSTANCE=instance
return instance
}
}
}
}
my dao as follows
#Dao
interface UserDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addUser(user:User) :Long
#Query("SELECT * from user_data")
fun fetchUserData(): Flow<List<User>>
}
my repository as follows
class UserRepository(private val userDao:UserDao) {
suspend fun addUser(user:User){
val rowid:Long=userDao.addUser(user)
}
fun readAllUserData():Flow<List<User>>
{
val temp=userDao.fetchUserData()
return temp
}
}
my view model as follows
class UserViewModel(application: Context) :ViewModel() {
private lateinit var _readAllData:MutableLiveData<List<User>>
private var repository: UserRepository
private var _firstName:String="Akshay"
val firstName:String get()=_firstName
private var _lastName:String=""
val lastName:String get()=_lastName
private var _age:String = ""
val age:String get()=_age
val readAllData:LiveData<List<User>> get()=_readAllData
init {
val userDao= UserDatabase.getDataBaseInstance(application).userDao()
repository=UserRepository(userDao)
}
fun setFirstName(name:String)
{
_firstName=name
}
fun setLastName(name:String)
{
_lastName=name
}
fun setAge(name:String)
{
_age=name
}
fun addUserToDB()
{
viewModelScope.launch(Dispatchers.IO) {
println("Inside addUserDB")
repository.addUser(User(0,_firstName,_lastName,_age.toInt()))
}
}
fun readDataFromUserTable():LiveData<List<User>>
{
return repository.readAllUserData().asLiveData()
}
}
class UserViewModelFactory(private val context: Context):ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass.isAssignableFrom(UserViewModel::class.java))
{
return UserViewModel(context)as T
}
throw IllegalArgumentException("Unknown class")
}
my fragment as follows
class ListFragment : Fragment() {
private var binding:FragmentListBinding?=null
private val sharedViewModel:UserViewModel by activityViewModels {UserViewModelFactory(requireContext())}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val fragmentBinding=FragmentListBinding.inflate(inflater,container,false);
binding=fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
viewModel=sharedViewModel
lifecycleOwner=viewLifecycleOwner
listFragment=this#ListFragment
ToastCount()
}
}
private fun ToastCount() {
var temp=sharedViewModel.readDataFromUserTable()
Toast.makeText(requireContext(), temp.size.toString(),Toast.LENGTH_SHORT).show()
}
fun addNewUser()
{ findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
}
I can add data I see data in my database inspector but I can not read data it always return me null in my ViewModel ... I have added all necessary dependencies somebody please guild me what is it I am doing wrong? Thanks in advance

#subcomponent.factory method is missing parameters for required modules or subcomponents

I am injecting Presenter to BookDashboard(Activity) and BookDashboardPresenter class require an MvpView interface in it's constructor .
When i run
AppComponent
#Component(
modules = [
AndroidInjectionModule::class,
ActivityBuilder::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun addBookEngine(#BindsInstance bookEngineModule: BookEngineModule) :Builder
fun build(): AppComponent
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = {BookEngineModule.class})
public abstract BookDashboard bindBookDashboard();
}
BookEngineModule.kt
#Module
class BookEngineModule(val mvpView: BookDashboardContract.MvpView){
#Provides
fun providePresenter():BookDashboardContract.Presenter{
return BookDashboardPresenter(mvpView)
}
}
BookDashboardContract
interface BookDashboardContract {
interface MvpView{
fun displayBooks()
fun showProgress()
fun hideProgress()
}
interface Presenter{
fun fetchedBooks()
}
}
BookDashboardPresenter.kt
class BookDashboardPresenter #Inject constructor(val viewContract:BookDashboardContract.MvpView) : BookDashboardContract.Presenter{
val bookInteractor = BookInteractor(this)
override fun fetchedBooks() {
bookInteractor.fetchDataFromServer()
viewContract.displayBooks()
}
}
BookDashboard -> Activity
class BookDashboard : DaggerAppCompatActivity(),BookDashboardContract.MvpView{
#Inject
lateinit var presenter: BookDashboardContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
DaggerAppComponent.builder().addContext(this).
addBookEngine(BookEngineModule(this)).build()
super.onCreate(savedInstanceState)
///presenter.fetchedBooks()
}
override fun displayBooks() {
Toast.makeText(this,"Books Displayed",Toast.LENGTH_LONG).show()
}
override fun showProgress() {}
override fun hideProgress() {}
}
But when I build the project I am having below error
ActivityBuilder_BindBookDashboard.java:24: error: #Subcomponent.Factory method is missing parameters for required modules or subcomponents: [quiz.mania.trivia.mcq.question.di.BookEngineModule]
interface Factory extends AndroidInjector.Factory<BookDashboard> {}
DaggerClass
#Module(subcomponents = ActivityBuilder_BindBookDashboard.BookDashboardSubcomponent.class)
public abstract class ActivityBuilder_BindBookDashboard {
private ActivityBuilder_BindBookDashboard() {}
#Binds
#IntoMap
#ClassKey(BookDashboard.class)
abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
BookDashboardSubcomponent.Factory builder);
#Subcomponent(modules = BookEngineModule.class)
public interface BookDashboardSubcomponent extends AndroidInjector<BookDashboard> {
#Subcomponent.Factory
interface Factory extends AndroidInjector.Factory<BookDashboard> {}
}
}
above class can't resolve BookEngineModule and BookDashboard
What am i missing ?