Passing parameters at runtime dagger 2 - java

I have a module, UserModule that takes in a string and provides a user object.
UserModule:
#Module
class UserModule(val name: String) {
#Provides
fun provideUser() : User = User(name = name)
}
And a ViewModelComponent that has UserModule as one of its components
ViewModelComponent:
#Singleton
#Component(modules = [UserModule::class])
interface ViewModelComponent {
fun inject(activity: MainActivity)
}
Normally I would provide the component in my application like this:
class MainApplication : Application() {
lateinit var component: AppComponent
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.builder()
.userModule(UserModule("Name"))
.build()
}
}
And reference it in my activity like this:
class MainActivity : AppCompatActivity() {
#Inject lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MainApplication).component.inject(this)
}
However, this assumes that the value of UserModule name is known at runtime and is hardcoded into MainApplication, however, in reality, the value of name is obtained from MainActivity.
The only solution I can think of is to build the dependency graph in MainActivity so I am able to pass name like this:
class MainActivity : AppCompatActivity() {
#Inject lateinit var user: User
val newUserName = "NewName"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
component = DaggerAppComponent.builder()
.userModule(UserModule(newUserName))
.build()
component.inject(this)
}
This solution seems very unefficient but it is the only way I can inject dynamic parameters in Dagger 2. Is there a better way to achieve this

I just ran into this issue aswell, first solution that I thought of was to create some "static holder" class that would hold dynamic parameters and it would be developers responsibility to update this holder at the right time - before injection happens I guess. I didnt actually try it though and it doesnt seem to be too clean either.
Anway after a bit of googling I found this article: https://proandroiddev.com/dagger-2-module-parameters-7c820d944a which seems to be more "dagger oriented" solution, didnt try that either, but it's something you can start with.
I would only comment this but I dont have required reputation...

Related

Android pass Edit Text data onChange from Activity to Fragment

I have a main activity with a heading and a search field (edit text), I want to be able to search and the results are immediately shown in the fragment, like an onChange instead of waiting for the user to click a button to filter results. (which is in the activity).
I can get it working if I include the Edit Text in my fragment too, but I don't want it that way for design purposes, I'd like to retrieve the user values as they are typed from the activity, and get them in my fragment to filter results
I've tried Bundles but could not get it working, and also not sure If i could use Bundles to get the results as they are being input.
Here's a screenshot to help understand better
You can make it happen using ViewModel + MVVM architecture.
MainActivity:
binding.editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
viewModel.updateSearchText(s)
}
})
ViewModel:
private val _searchText = MutableLiveData<Editable?>()
val searchText: LiveData<Editable?> get() = _searchText
fun updateSearchText(text: Editable?) {
_searchText.value = s
}
Fragment:
searchText.observe(viewLifecycleOwner) {
// TODO: handle the searched query using [it] keyword.
}
If you don't know what View Model is or how to implement it, use the official Google tutorial: https://developer.android.com/codelabs/basic-android-kotlin-training-viewmodel
Another way to achieve this (besides using an Android ViewModel) is use the Fragment Result API.
For instance, if you place the EditText into a fragment (let's call it QueryFragment), you can get the result of the QueryFragment in your SearchResults fragment like so:
// In QueryFragment
editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(s: Editable?) {
setFragmentResult("searchQueryRequestKey", bundleOf("searchQuery" to s.toString()))
}
})
// In SearchResultsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve the searched string from QueryFragment
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("searchQueryRequestKey") { requestKey, bundle ->
val searchQuery = bundle.getString("searchQuery")
// Perform the search using the searchQuery and display the search results
}
}

NoSuchMethodException when inherit abstract class in Activity

I'm facing this issue on my apps :
Caused by java.lang.NoSuchMethodException: <init> []
at java.lang.Class.getConstructor0(Class.java:2332)
at java.lang.Class.getConstructor(Class.java:1728)
at androidx.fragment.app.Fragment.a(Fragment.java:15)
at androidx.fragment.app.FragmentContainer.instantiate(FragmentContainer.java)
at com.example.DefAct.colorStatusBar(DefAct.java)
at com.example.MainActivity.initWorkers(MainActivity.java)
Which caused this error :
Caused by androidx.fragment.app.Fragment$e: Unable to instantiate fragment f.a.a.a.x.u.a: could not find Fragment constructor
at androidx.fragment.app.Fragment.a(Fragment.java:66)
at androidx.fragment.app.FragmentContainer.instantiate(FragmentContainer.java)
at androidx.fragment.app.FragmentManagerImpl$6.instantiate(FragmentManagerImpl.java:2)
at com.example.DefAct.colorStatusBar(DefAct.java)
at com.example.MainActivity.initWorkers(MainActivity.java)
So, I have an abstract class DefAct.kt :
abstract class DefAct: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
colorStatusBar()
}
private fun colorStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = ContextCompat.getColor(this, R.color.blue)
}
}
}
Then, I call this abstract class into my MainActivity.kt :
class MainActivity: DefAct(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
.....
}
Some ideas that I think might become a solution for my problem :
Changing the colorStatusBar() method on MyDefaultAct.kt into public
Create empty constructor
Things I've done :
Read documentation about the abstract class, fragment and its constructor
Trying to create a constructor but my Activity doesn't have any fragments except for only calling
dialog.show(supportFragmentManager, SomeTags.TAG)
I don't know exactly how to reproduce the test case that caused these logs. But when I read the log, the exception appeared right after calling the colorStatusBar(). Please kindly help me to solve this issue, I've been struggling for 4 hours. If I missed something with my code, please kindly tell me. Thank you

Android registerOnSharedPreferenceChangelistener only triggered a few times after that not working

Hello so I have the problem that I registered a onSharedPreferenceChangeListener in my MainActivity. The only preference I have is a ListPreference with 3 different options. So at the start of the program it still gets triggered the first - 3 times mostly, sometimes it doesn't even trigger at the beginning. I don't think that's how it is supposed to work so my code is down below if more is needed just write a comment of a specific part.
// (from MainActivity)
this.sharedPreferences.registerOnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences, s: String ->
var value = sharedPreferences.getString("location", "")
controller.setLocation(value, this)
}
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}
Found the solution by myself, for everyone who is having an issue like that. It's probably the scope. The listener gets lost when you are switching between activities.
How to fix: put the listener block as a global variable and just register it in your "onCreate" method.

Understanding the lifecycle of DataBinding

My Activity:
class PlayerDetails : AppCompatActivity(), View.OnClickListener {
private lateinit var binding: ActivityPlayerDetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_player_details)
}
I'm trying to understand how the data binding process works, this is how I understand it:
private lateinit var binding: ActivityPlayerDetailsBinding
instantiates the ViewDataBinding object.
binding = DataBindingUtil.setContentView(this,
R.layout.activity_player_details) is in 2 parts:
DataBindingUtil.setContentView(this,
R.layout.activity_player_details) sets the content view to the given layout
It then returns the binding object to the binding variable (binding = ...) which can then be used to access views in the layout.
Is this an accurate way of describing how the code is working? I found the source code for DataBindingUtil.java hard to understand. Mostly because setContentView() is being called even though it appears to be assigned instead binding = ....
DataBindingUtil.setContentView(this, R.layout.activity_player_datails) do almost same thing to return binding object. Although DataBindingUtils.setContentView call activity.setContentView before returning.
Instead, I usually override setContentView to make sure assign binding object into variables and sets content to the given layout.
override fun setContentView(layoutResID: Int) {
binding = DataBindingUtil.inflate(LayoutInflater.from(context), layoutResID, null, false)
super.setContentView(mBinding.root)
}

How to set error label with TextInputLayout in the viewmodel with kotlin

I need to do some input validation for my TextInputEditText that is wrapped with TextInputLayout.
I'd like errors to appear below the line if the input is done in wrong format.
All the logic is done in the viewmodel instead of the view(fragment or activity). But I can't seem to access the view through viewmodel, for instance:
textinputlayout.setError("error") doesn't work in the viewmodel
and layout.findViewbyId(layoutId) doesn't work in the viewmodel either.
Any idea?
used below code to set error in TextInputLayout..
class SpinerActivity :AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.spiner)
setEdittext()
}
private fun setEdittext() {
var textError:TextInputLayout=findViewById(R.id.amEt1)
textError.error="Please Enter Name"
}
}

Categories