I have a parcelable team class
#Parcelize
class Team(var name: String, var teamMembers: List<String>, var id: UUID): Parcelable
I have a service that returns a list of (currently hardcoded) Teams:
#Module
class TeamInfoModule #Inject constructor(): ITeamInfoModule {
#Provides
override fun getAllTeamData(): List<Team> { ... }
}
I want to be able to pass this list of teams into a Fragment from an activity like so:
class MainActivity: AppCompatActivity() {
#Inject
lateinit var teamInfoModule: TeamInfoModule;
lateinit var team: Team;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerServiceModuleComponent.create().inject(this)
val bundle = Bundle()
val teamArrayList: List<Team> = this.teamInfoModule.getAllTeamData()
val homeFragment = HomeFragment()
bundle.putParcelable("teamData", teamArrayList)
homeFragment.arguments = bundle
}
}
This throws an error of: Type Mismatch. Required: Parcelable? Found: List<Team>.
I know that that a single team can be passed to my Fragment as it doesn't throw an error.
My question is, is there a utility that I haven't found that can somehow serialize a List to a Parcelable? I had the idea of creating a custom TeamListClass that also implements #Parcelize but I wanted to ask here before I went off and wrote code that I didn't need. Maybe something similar to a JS' Array.map() that will pass each Parcelable into the bundle?
You should use:
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))
Convert the list to arrayList using ArrayList(teamArrayList)
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))
Related
In my activity, I have multiple variables being initiated from Intent Extras. As of now I am using ViewModelFactory to pass these variables as arguments to my viewModel.
How do I eliminate the need for ViewModelFacotory with hilt
Here are two variables in my Activity class
class CommentsActivity : AppCompatActivity() {
private lateinit var viewModel: CommentsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
val contentId = intent.getStringExtra(CONTENT_ID_FIELD) //nullable strings
val highlightedCommentId = intent.getStringExtra(HIGHLIGHTED_COMMENT_ID_RF) //nullable strings
val commentsViewModelFactory = CommentsViewModelFactory(
contentId,
highlightedCommentId
)
viewModel = ViewModelProvider(this, commentsViewModelFactory[CommentsViewModel::class.java]
}
}
Here is my viewModel
class CommentsViewMode(
contentId : String?,
highlightedCo;mmentId : String?,
) : ViewModel() {
//logic code here
}
My app is already set up to use hilt but in this case How can I pass these 2 variables and eliminate the viewModelFactory entirely
The trick is to initialize those variables only once, while the activity can be created multiple times. In my apps, I use a flag.
View model:
class CommentsViewModel : ViewModel() {
private var initialized = false
private var contentId : String? = null
private var highlightedCommentId : String? = null
fun initialize(contentId : String?, highlightedCommentId : String?) {
if (!initialized) {
initialized = true
this.contentId = contentId
this.highlightedCommentId = highlightedCommentId
}
}
//logic code here
}
Also, you should know that there is an open issue in dagger project exactly for this capability:
https://github.com/google/dagger/issues/2287
You're welcome to follow the progress.
If you want to use hilt effectively u can follow this steps
Use #HiltViewModel in your view model
#HiltViewModel
class MyViewModel #inject constructor(private val yrParameter): ViewModel {}
Also you no longer need any ViewModelFactory! All is done for you! In your activity or fragment, you can now just use KTX viewModels() directly.
private val viewModel: MyViewModel by viewModels()
Or if you want to use base classes for fragment and activity you can use this code to pass viewModel class
abstract class BaseFragment<V: ViewModel, T: ViewDataBinding>(#LayoutRes val layout: Int, viewModelClass: Class<V>) : Fragment() {
private val mViewModel by lazy {
ViewModelProvider(this).get(viewModelClass)
}
}
here I am trying to inject the adapter in activity via field injection. Adapter has a parameter(list).
Can somebody assist me here? i am facing compile time error
cannot be provided without an #Provides-annotated method.
Please refer below code
#AndroidEntryPoint
class RecipeActivity() : PostLoginActivity() {
var TAG = MainActivity::class.java.simpleName
private lateinit var binding: ActivityRecipeBinding
private val viewModel: RecipeViewModel by viewModels()
#Inject lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
class RecipeAdapter #Inject constructor(list: MutableList<RecipeModel> ) :
BaseAdapter<RecipeModel>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<RecipeModel> {
return RecipeViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_recipe, parent, false), this
)
}
override fun onBindViewHolder(holder: BaseViewHolder<RecipeModel>, position: Int) {
holder.bindData(baseList[position])
}
}
data class RecipeModel(
var title: String,
var imageType: String,
var url: String
) : Item()
In order to Inject a class, Hilt/Dagger needs to understand exactly how to Inject it. In your project, you should have a 'Module' object. Within here, you can create #Provides methods, which tell Hilt/Dagger exactly what a class looks like so that it can be injected (find out more here).
For example, to provide a class that implements some Android Retrofit services, you might have a module that looks like:
#Module
#InstallIn(ActivityComponent::class)
object AnalyticsModule {
#Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
In this example, we can now use #Inject for an AnalyticsService, as Hilt/Dagger now knows how to make one!
In your scenario, it looks like your adapter needs to be constructed with a list of RecipeModels. As you will unlikely have access to this data at the Module level, I don't think you want to be injecting the Adapter like this? Simply creating it in the Activity should be sufficient for what you need!
Something like this:
private var adapter: RecipeAdapter? = null // OR
lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = RecipeAdapter(viewModel.recipeModels)
}
As a rule of thumb, it is generally more common to use injection for services, factories and view models rather than UI elements like adapters, as these UI elements need to often be constructed with actual data which isn't available in an application's Hilt/Dagger module.
Well as an error suggests you need to have a module in which you provide default construction of you adapter.
Example:
#Module
#InstallIn(ActivityComponent::class)
object AppModule {
#Provides
fun provideRecipeAdapter(
list: MutableList<RecipeModel>
): RecipeAdapter {
return RecipeAdapter(list)
}
}
This is just an example of what you are missing, not actual working code. For more details of how to create these modules look at the documentation
I am currently building an app and I have added Dagger Hilt in order to define a single class to access data. the injection seems working fine but I am not able to store a value in the data class I use.
I have created a Singleton first, which is used by the code to set/get value from a data structure.
#Singleton
class CarListMemorySource #Inject constructor() : CarListInterface {
private var extendedCarList: ExtendedCarList? = null
override fun setListOfVehicles(listOfVehicles: List<item>)
{
extendedCarList?.listOfVehicles = listOfVehicles
}
}
When I am calling setListOfVehicles the listOfVehicules contains 10 items but
The data structure ExtendedCarList is defined as below:
data class ExtendedCarList(
var listOfVehicles: List<item>
)
The Singleton in passed using Hilt like for example in the viewModel below:
#HiltViewModel
class HomeScreenViewModel #Inject constructor(
private val carList: CarListMemorySource
): ViewModel() {
fun getList() {
--> DO SOMETHING TO Get A ListA
carList.setListOfVehicles(ListA)
}
}
And in the activity, using the viewModel, I am just doing this:
#AndroidEntryPoint
class HomeScreenActivity: AppCompatActivity() {
private val viewModel: HomeScreenViewModel by viewModels()
....
viewModel.getList()
....
}
Any idea why and how to fix it ?
Thanks
you never initialize extendedCarList.
extendedCarList?.listOfVehicles = listOfVehicles
above line is exactly the same as
if (extendedCarList != null) extendedCarList.listOfVehicles = listOfVehicles
But it never passes the null check.
I think just changing
private var extendedCarList: ExtendedCarList? = null
to
private val extendedCarList = ExtendedCarList()
might solve it
I am trying to use Dagger Hilt in order to inject a data class in my code. My goal is to be able to have a single data structure that I can inject everywhere.
I have defined a data class as below:
data class UserInfo #Inject constructor(
var lastname: String,
var firstname: String,
var email: String)
I have tried to use this data class in my MainActivity as below:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject
lateinit var userInfo: UserInfo
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initValue()
}
fun initValue() {
userInfo.email = "ste#gmail.com"
}
I keep getting error like
public abstract static class SingletonC implements DriverApplication
I tried to add Singleton but it's also generating the issue.
I just try to have one single data class to store information and use this information everywhere in my code.
Any idea ?
I got some issues with Kotlin when translating my android project from java to Kotlin.
Say i have interface I and interface O which extends interface I.
interface I{
}
interface O: I{
}
And generic class A which have generic parameter V that extends interfaceI, and generic class B which extends class A:
abstract class A<V: I> {
}
class B : A<O>() {
}
When i'm trying to create such property:
val returnB: A<I>
get() = b
I'm getting compiler error 'required A, found B'. In Java this will work without any issues. How can i access this using Kotlin ?
I need to use this approach for Basic classes in my application.
BaseViewModel which have generic parameter for Navigator class:
abstract class BaseViewModel<N>(application: Application, val repositoryProvider:
RepositoryProvider) : AndroidViewModel(application) {
var navigator: N? = null
fun onDestroyView() {
navigator = null
}
open fun onViewAttached() {
}
}
BaseActivity class:
abstract class BaseActivity<T : ViewDataBinding, V : BaseViewModel<BaseNavigator>> : AppCompatActivity(),
BaseFragment.Callback, BaseNavigator {
// .......
private var mViewModel: V? = null
/**
* Override for set view model
* #return view model instance
*/
abstract val viewModel: V
// .......
}
BaseNavigator interface uses for VM - View communication:
interface BaseNavigator {
fun invokeIntent(intent: Intent?, b: Bundle?, c: Class<*>?,
forResult: Boolean, requestCode: Int)
fun replaceFragment(fragment: Fragment, addToBackStack: Boolean)
fun showDialogFragment(fragment: DialogFragment?, tag: String?)
fun showToast(message: String?)
}
Here example code where i'm extending these classes:
AuthViewModel:
class AuthViewModel(context: Application, repositoryProvider: RepositoryProvider) :
BaseViewModel<AuthNavigator>(context,repositoryProvider) {
// ....
}
AuthNavigator:
interface AuthNavigator : BaseNavigator {
fun requestGoogleAuth(code: Int)
fun requestFacebookAuth(callback: FacebookCallback<LoginResult>)
}
And AuthActivity class where error was appeared:
class AuthActivity : BaseActivity<ActivityAuthBinding, BaseViewModel<BaseNavagator>>(),
GoogleApiClient.OnConnectionFailedListener, AuthNavigator {
#Inject
lateinit var mViewModel: AuthViewModel
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel // Required:BaseViewModel<BaseNavigator> Found: AuthViewModel
}
I'm also tried to change generic parameter in AuthActivity from BaseViewModel to AuthViewModel, but compiler throws error 'required BaseViewModel'.
And i tried to change
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel
to
override val viewModel: AuthViewModel
get() = mViewModel
but in this case compiler throws error 'Property type is 'AuthViewModel', which is not a subtype type of overridden'.
update:
That works when i add out property to BaseViewModel:
BaseViewModel<out N : BaseNavigator>
But in this case i can only create
private var navigator: N? = null
which i need to be public so i can set it in the Activity class. Can i create public setter for this property? When i'm trying to create setter an error occurs:
private var navigator: N? = null
fun setNavigator(n: N) { // error: Type parameter N is declared as 'out' but occurs in 'in' position in type N
navigator = n
}
It looks like you are expecting the type parameter to behave covariantly. Kotlin uses declaration-site variance. If you do not specify the variance, generic type parameters are invariant.
In other words, right now there is no relationship between A<I> and A<O>. But if you declare
abstract class A<out V : I>
then A<O> is a subtype of A<I>.
(There is also <in> for contravariance, which works the other way around. See https://kotlinlang.org/docs/reference/generics.html for more details.)