Sending complex object into Fragment android? - android

I have to send complex object into a fragment.
I used this code but it does not work.
companion object {
#JvmStatic
fun newInstance(param1: CustomerWithAccounts, param2: String) = TransactionsListFragment().apply {
arguments = Bundle().apply {
putParcelable(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
println("$param1 $param2")
}
}
private const val dialogTag = "transaction_list_dialog"
fun display(fragmentManager: FragmentManager): TransactionsListFragment {
val transactionListDialog = TransactionsListFragment()
transactionListDialog.show(fragmentManager, dialogTag)
return transactionListDialog
}
}
and I send it like this.
private val onItemClickListener = object : TransactionAdapter.OnItemClickListener {
override fun onItemClick(view: View?, obj: CustomerWithAccounts?, position: Int) {
if (obj != null) {
val fragmentManager: FragmentManager = childFragmentManager
val fragment = TransactionsListFragment
fragment.newInstance(obj, "DIAA")
fragment.display(fragmentManager)
}
}
}
This is my Modal class
class CustomerWithAccounts(
#Embedded val customerAccounts: CustomerAccounts,
#Relation(parentColumn = "customerOwnerId", entityColumn = "customerId")
val customer: Customer
)
How to do that please help.

Writing code that can be generated from XML is questionable.
Use navigation with safe-args plugin, which is rather the framework way of passing Object.It's a code-generator plugin which utilizes the arguments defined in the navigation graph XML.
buildscript {
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.3"
}
}
Per module:
// apply plugin: "androidx.navigation.safeargs"
apply plugin: "androidx.navigation.safeargs.kotlin"
The outcome are generated bindings alike: TransactionFragmentArgs implements NavArgs.
See: Pass data between destinations for how to declare these argument-definitions ...

You messed up in creating Object . you are basically creating two objects right now . Fix it .
private val onItemClickListener = object : TransactionAdapter.OnItemClickListener {
override fun onItemClick(view: View?, obj: CustomerWithAccounts?, position: Int) {
if (obj != null) {
val fragment = TransactionsListFragment.newInstance(obj, "DIAA")
fragment.display(childFragmentManager)
}
}
}
Well you need to fix your display method too . Why r u creating another Object in Display ?
fun display(fragmentManager: FragmentManager) {
show(fragmentManager, dialogTag)
}

Related

Kotlin combine two callbackflows depending from each other

I have data class, MySection, which has the ID in it. I have another data class MyArticle whdoesn'ton't care about its content
To get the Section,
private fun getSectionFlow(): Flow<List<MySection>> =
callbackFlow {
val listCallBackFlow = object : SectionCallback<List<MySection>>() {
override fun onSuccess(sections: List<MySection>) {
trySend(sections)
}
override fun onError(errorResponse: ErrorResponse) {
close(Exception(errorResponse.reason))
}
}
provider.getSections(
listCallBackFlow
)
awaitClose()
}
And for Article,
private fun getArticleFlow(id: Long): Flow<List<MyArticle>> =
callbackFlow {
val listCallBackFlow = object : ArticleCallback<List<MyArticle>>() {
override fun onSuccess(sections: List<MyArticle>) {
trySend(sections)
}
override fun onError(errorResponse: ErrorResponse) {
close(Exception(errorResponse.reason))
}
}
provider.getArticle(
id, // to get the article list it needs section id
listCallBackFlow
)
awaitClose()
}
And finally, all values need to bind in List<MySectionWithArticle>
data class MySectionWithArticle (
val id: Long,
val title: String,
var myArticlesList: List<MyArticle> = emptyList()
)
So the inputs to getArticleFlow(id) should be the IDs of the values from getSectionFlow()
So the main question is how can I combine two callbackflow functions in such a way it gives me combine values MySection ,List<MyArticle> to MySectionWithArticle?
So for List<MySection>, each MySection need to call getArticleFlow(id) function and then List<MyArticle> combine with MySectionWithArticle data class
How can I achieve this functionality ?

Working with multiple repositories in one ViewModel on kotlin

Tell me, please, how to make it more correct so that the ViewModel supports working with the desired repository, depending on the viewmodel's parameter? Android application should display a list of requests, requests are of different types. I want to use one fragment for request of different types and in one model I want universally work with a repository that will pull out requests of the required type from the database (Room).
I made a common interface for repositories:
interface RequestRepository<T> {
fun getRequests(): LiveData<List<T>>
fun getRequestById(requestId: String): LiveData<T>
suspend fun insertRequests(requests: List<T>)
suspend fun deleteRequest(request: T)
suspend fun deleteAllRequests()
}
This is one of the repositories:
class PaymentRequestRepository private constructor(private val paymentRequestDao: PaymentRequestDao) : RequestRepository<PaymentRequest> {
override fun getRequests() = paymentRequestDao.getRequests()
override fun getRequestById(requestId: String) = paymentRequestDao.getRequestById(requestId)
override suspend fun insertRequests(requests: List<PaymentRequest>) {
paymentRequestDao.deleteAll()
paymentRequestDao.insertAll(requests)
}
override suspend fun deleteRequest(request: PaymentRequest) = paymentRequestDao.delete(request)
override suspend fun deleteAllRequests() = paymentRequestDao.deleteAll()
companion object {
// For Singleton instantiation
#Volatile private var instance: PaymentRequestRepository? = null
fun getInstance(paymentRequestDao: PaymentRequestDao) =
instance ?: synchronized(this) {
instance ?: PaymentRequestRepository(paymentRequestDao).also { instance = it }
}
}
}
How in the ViewModel to work with the necessary repository depending on the type of request?
class RequestListViewModel(application: Application, val requestType: RequestType): AndroidViewModel(application) {
//lateinit var paymentRequestRepository: PaymentRequestRepository
//lateinit var serviceRequestRepository: ServiceRequestRepository
lateinit var requestRepository: RequestRepository<BaseRequestDao<Request>>
...
init {
val database = AgreementsDatabase.getDatabase(application)
when (requestType) {
RequestType.MONEY -> {
val paymentRequestDao = database.paymentRequestsDao()
requestRepository = PaymentRequestRepository.getInstance(paymentRequestDao)
}
RequestType.SERVICE -> {
val serviceRequestDao = database.serviceRequestsDao()
requestRepository = ServiceRequestRepository.getInstance(serviceRequestDao)
}
RequestType.DELIVERY -> {
val deliveryRequestsDao = database.deliveryRequestsDao()
requestRepository = DeliveryRequestRepository.getInstance(deliveryRequestsDao)
}
}
_requests = requestRepository.getRequests()
updateRequests();
}
}
** When creating a repository, I get a type mismatch error: **
requestRepository = PaymentRequestRepository.getInstance(paymentRequestDao)
Tell me how is this done correctly?

MutableLiveData ArrayList is empty even after postValue() Kotlin

I am now stuck and currently wondering why my mutable arraylist returns null even if it is being updated with postvalue(). I tried to display it using Toast and it displayed [] which I think is null. It had no space in between so it looked like a box. I did toString() it as well in order to show the text. How would I be able to solve this problem?
Here is my Main Activity:
val list = ArrayList<String>()
list.add("text1")
list.add("text2")
val viewmodel = ViewModelProviders.of(this).get(viewmodel::class.java)
viewmodel.Testlist.postValue(list)
ViewModel:
class viewmodel: ViewModel() {
val Testlist: MutableLiveData<ArrayList<String>> = MutableLiveData()
init {
Testlist.value = arrayListOf()
}
}
Fragment:
Top area:
activity?.let {
val viewmodel = ViewModelProviders.of(this).get(viewmodel::class.java)
observeInput(viewmodel)
}
Bottom area:
private fun observeInput(viewmodel: viewmodel) {
viewmodel.Testlist.observe(viewLifecycleOwner, Observer {
it?.let {
Toast.makeText(context, it.toString(), Toast.LENGTH_LONG).show()
}
})
}
You post the value to the LiveData object in the activity's viewmodel, which isn't the same instance as the fragment's viewmodel. Let's take look at the way you instantiate the viewmodel in your fragment:
activity?.let {
// activity can be refered by the implicit parameter `it`
// `this` refers to the current fragment hence it's the owner of the view model
val viewmodel = ViewModelProviders.of(this).get(viewmodel::class.java)
observeInput(viewmodel)
}
To get a viewmodel that is shared between your activity and fragment you have to pass the activity as its owner:
activity?.let { val viewmodel = ViewModelProviders.of(it).get(viewmodel::class.java) }
Probably you can see developer guide example to resolve your problem
https://developer.android.com/topic/libraries/architecture/viewmodel.html#kotlin
// shared viewmodel
class SharedViewModel : ViewModel() {
private val usersList: MutableLiveData<List<String>>()
fun getUsers(): LiveData<List<String>> {
return usersList
}
fun setUsers(users: List<String>) {
usersList.value = users
}
}
// Attach ViewModel In Activity onCreate()
val model = ViewModelProviders.of(this)[SharedViewModel::class.java]
val list = arrayListOf<String>()
list.add("user1")
list.add("user2")
model.setUsers(list)
// Get same ViewModel instance In fragment onCreateView()
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
You can use this :
fun <T : Any?> MutableLiveData<ArrayList<T>>.default(initialValue: ArrayList<T>) = apply { setValue(initialValue) }
and then use this function as below:
viewmodel.Testlist.default(ArrayList())
For me, I have a BaseActivity that other activities extend from it :
class UAppCompatActivity : AppCompatActivity() {
protected fun <T : Any?> MutableLiveData<ArrayList<T>>.default(initialValue: ArrayList<T>) = apply { setValue(initialValue) }
protected fun <T> MutableLiveData<ArrayList<T>>.addItem(item: T) {
val updatedItems = this.value as ArrayList
updatedItems.add(item)
this.value = updatedItems
}
protected fun <T> MutableLiveData<ArrayList<T>>.deleteItem(item: T) {
val updatedItems = this.value as ArrayList
updatedItems.remove(item)
this.value = updatedItems
}
...
have you used the same instance of your view model? or have you defined another view model in the fragment class? The issue could be that you're accessing a different instance of the view model and not the one were the MutableLiveData was updated

Can we pass arguments to Fragments more beautifully?

For every Fragment class I make, I add something like this:
companion object {
private const val PARAMETER_1 = "parameter1"
private const val PARAMETER_2 = "parameter2"
fun newInstance(parameter1: String, parameter2: Int) = MyDialog().apply {
arguments = bundleOf(
PARAMETER_1 to parameter1,
PARAMETER_2 to parameter2)
}
}
And then I add:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = arguments ?: return
property1 = args[PARAMETER_1]
property2 = args[PARAMETER_2]
}
This isn't horrific. But it is boilerplate that it would be great to get rid of.
Here's my attempt so far:
abstract class BaseFragment : Fragment() {
abstract val constructorArguments: List<KMutableProperty<*>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = arguments ?: return
constructorArguments.forEach {
val key = keyPrefix + it.name
val argument = args.get(key)
val clazz = it.javaClass
val typedArgument = clazz.cast(argument)
it.setter.call(typedArgument)
}
}
companion object {
const val keyPrefix = "ARGUMENT_"
fun newInstance(fragment: BaseFragment, vararg parameters: Any): BaseFragment {
val constructorArguments = fragment.constructorArguments
val parameterMap = mutableListOf<Pair<String, Any?>>()
constructorArguments.forEachIndexed { index, kMutableProperty ->
val key = keyPrefix + kMutableProperty.name
val parameter = parameters[index]
parameterMap.add(Pair(key, parameter))
}
val args = bundleOf(*parameterMap.toTypedArray())
fragment.arguments = args
return fragment
}
}
}
And then, in the actual fragment I can just have:
class MyFragment : BaseFragment() {
lateinit var myProperty: String
override val constructorArguments = listOf<KMutableProperty<*>>(
::myProperty
)
companion object {
fun newInstance(argument: String) = BaseFragment.newInstance(MyFragment(), argument)
}
}
This approach is far from perfect - especially the:
val parameter = parameters[index]
Does anyone know a better way to do this? Do you have some suggestions for how my approach can be improved? Or is this whole idea doomed to fail, and have I wasted a morning?
An 'answer' to this question is to use the Android Jetpack Navigation library. It provides SafeArgs, which greatly simplifies passing arguments to Fragments. See:
https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args
You can have a base fragment that defines a common args parameter
abstract class BaseFragment : Fragment() {
companion object {
const val ARGS_KEY = "__ARGS__"
}
fun <T: Parcelable> getArgs(): T = requireArguments().getParcelable(ARGS_KEY)
fun putArgs(args: Parcelable): Bundle = (arguments ?: Bundle()).apply {
putParcelable(ARGS_KEY, args)
}
}
Then
#Parcelize data class Args(val parameter1: String, val parameter2: Int)
companion object {
fun newInstance(args: Args) = MyDialog().apply {
putArgs(args)
}
}
And now you can do it like
class MyFragment: BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val args: Args = getArgs()
args.parameter2
}
}

Issues to pass objects as parameters from activity to fragment

I am trying to pass an object as a parameter from an activity to a fragment.
I already searched over hill and dale to find out how to do it, I tried many methods but none of them worked.
Here is what I tried :
// In my Activity, where I call my fragment
var bundle = Bundle()
bundle.putParcelable("restaurant", restaurant)
var fragment = Home2Fragment()
fragment.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.content, fragment, FRAGMENT_HOME).commit()
My IDE (Android Studio) underline "restaurant" in bundle.putParcelable line, telling me a type mismatch.
This is the same with the getArguments() methods, I get a type mismatch.
My class look like this :
#Parcel
class Establishment {
var title = ""
// More variables
fun loadFromJson(json: JsonObject) {
title = readString(json, TAG_TITLE)
// More API loads
}
companion object {
private const val TAG_TITLE = "title"
// More tags
}
}
I don't understand why my class is set as "Parcel" but is not recognized as a "Parcelable".
I am looking for an efficient way to pass parameters from an activity to a fragment.
Thanks a lot in advance.
EDIT :
As asked, I tried to make it as Serializable.
I can't make it Serializable, as "Serializable" does not exists.
But I tried to make it "SerializedName", but "This annotation is not applicable to target 'class' "
Your class should implement Parcelable this way:
class Establishment(val title: String) : Parcelable {
private constructor(p: Parcel) : this(
title = p.readString(),
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(title)
}
override fun describeContents() = 0
companion object {
#JvmField val CREATOR = object : Parcelable.Creator<Establishment> {
override fun createFromParcel(parcel: Parcel) = Establishment(parcel)
override fun newArray(size: Int) = arrayOfNulls<Establishment>(size)
}
}
fun loadFromJson(json: JsonObject) {
title = readString(json, TAG_TITLE)
// More API loads
}
}
So with your help I finally found out how to do this.
In my fragment, I modified my newInstance() fun :
companion object {
fun newInstance(restaurant : Establishment) : Home2Fragment {
val fragment = Home2Fragment()
val args = Bundle()
args.putParcelable("restaurant", restaurant)
fragment.arguments = args
return (fragment)
}
}
This was not very difficult, it was before as my IDE was telling me that my object "Establishment" was not a Parcelable Object.
So the real problem was to set "Establishment" as a Parcelable Object.
It gave me hard time so I decided to use Parcelable Generator plugin, as adviced by TpoM6oH.
My class Establishment now look like this :
#Parcel
class Establishment() : Parcelable {
var title = ""
// More var
fun CREATOR(): Parcelable.Creator<Establishment> = object : Parcelable.Creator<Establishment> {
override fun createFromParcel(p0: android.os.Parcel?): Establishment {
return Establishment()
}
override fun newArray(p0: Int): Array<Establishment> {
return null as Array<Establishment>
}
}
constructor(parcel: android.os.Parcel) : this() {
this = parcel.readString()
// More var
}
fun loadFromJson(json: JsonObject) {
title = readString(json, TAG_TITLE)
// More load from API
}
companion object {
private const val TAG_TITLE = "title"
}
override fun writeToParcel(parcel: android.os.Parcel, flags: Int) {
parcel.writeString(title)
// More var
}
override fun describeContents() : Int {
return 0
}
}
The plugin Parcelable Generator added some mysterious functions in my class, and "Establishment" (line 2) is still a problem in my IDE opinion ("This class implements Parcelable but does not provide a CREATOR field").
But the code DOES compile ! And it is working !
Thanks everyone for your advice.

Categories

Resources