Safe Args: use list of parcelables - android

I am using the Safe Args plugin with the new Navigation components for my Android project. Now I have an argument that is an array list of parcelables, is there a way to use this with the Safe Args Plugin?
Something like app:argType=ParcelableArray. This should be possible since there are bundle methods like putParcelableArrayList().

Yes, since version 1.0.0-alpha08 you can now pass arrays of parcelable objects like this:
<argument
android:name="users"
app:argType="com.navigation.test.User[]"/>
For passing arrays of primitive types use for e.g. app:argType="integer[]"

Currently i don't think there is a simple way to use list of parcelables with safe args,
But i have found some "hack" to make this work.
For example, i have object 'User' and it parcelable, i am declaring a new parcelable object 'Users' that extending ArrayList().
#Parcelize
data class User(var name: String, val age: Int): Parcelable
#Parcelize
class Users: ArrayList<User>(), Parcelable
Now i can set 'Users' as argument in navigation
<argument
android:name="users"
app:argType="com.navigation.test.Users"/>
And passing array list of parcelables between destinations:
val user=User("Alex", 36)
val users= Users()
users.add(user)
val action=MainFragmentDirections.actionMainFragmentToSecondFragment(users)
NavHostFragment.findNavController(this#MainFragment).navigate(action)
And to retrieve them on other destination:
val users=SecondFragmentArgs.fromBundle(arguments).users
val user=users[0]
txtViewName.text=user.name
txtViewAge.text="${user.age}"
Update:
Support to list of objects coming in alpha8:
https://issuetracker.google.com/issues/111487504
Update 2:
The approach mentioned above will not work in case the activity is recreated, as #Parcelize will not be able to store/restore the list.
The object will be store in the state bundle, however, it will store an empty list of objects.

An improvement to #LaVepe suggestion: as for Android Studio 3.4.2 you can pass Parcelable array with in-built feature of navigation editor by specifying Arguments for chosen destination. Just check 'Array' option after choosing your custom Parcelable class which should not be wrapped in any collection beforehand:
EDIT
Here how it looks in xml:
<argument
android:name="items"
app:argType="com.company.domain.Item[]" />
In your Fragment/Activity code you might strictly pass a typed array of model Parcelable items. If you have non-array collection and write in Kotlin, it may be achieved with (if you have list beforehand) list.toTypedArray().

Related

#Parcelize and Superclass, doubling data because of constructor requirement

I have a Result parcelable class that is supposed to serve as a container for a key and a parcelable data. This is how I define it:
#Parcelize
open class Result<out T : Parcelable>(val key: String, val data: T?) : Parcelable
The problem is that when defining the child class, in order for #Parcelize to work, I need to add val to the object in the constructor, essentially making the data being written twice to the parcel twice. Here is an example:
#Parcelize
class LessonFinishedResult(private val lessonActivityData: LessonActivityData) :
Result<LessonActivityData>(LessonActivityData.LESSON_KEY, lessonActivityData), Parcelable
I would like to have this:
#Parcelize
class LessonFinishedResult(lessonActivityData: LessonActivityData) :
Result<LessonActivityData>(LessonActivityData.LESSON_KEY, lessonActivityData), Parcelable
But it is not allowed. Is there a smart way I can get around this? And another thing, is there a way I can avoid having to manually use Result<LessonActivityData> and have the type be inferred automatically from the object being passed?
Thanks!
essentially making the data being written twice to the parcel twice
It isn't; the code generated by #Parcelize doesn't care about the superclass at all. It can even not be Parcelizable. Only the primary constructor parameters (in this case val lessonActivityData) are written/read, and when reading it's enough to pass the correct parameters to the superclass constructor.

Android ViewModels: Should dataclasses expose livedata properties?

Given this scenario:
Model
data class User(var id: int, var name: String)
View Model
val Users: LiveData<List<User>>
val SelectedUser: LiveData<User>
fun changeSelectedUserName(){SelectedUser.Name = "foo"}
UI
<android:TextView Text="#{viewmodel.SelectedUser.name}"/>
<android:Button Text="Change!" onClik="#{() -> viewmodel.changeSelectedUserName()}"/>
When user clicks 'Change!' button the textview won't change because the 'name' field is not LiveData.
Questions
Should data class re-expose its fields as LiveData too?
If so, what will happen to regular fields? Are they replaced or keeped with another naming convention?
What is the correct naming convention if I'm using retrofit? So I can keep both the interface methods and LiveData working with the less amount of code?
Your issue is that you are modifying the User object properties directly, rather than update the LiveData.
For this to work, you have to do one of the following:
Make User extend BaseObservable, and invoke notifyPropertyChanged(BR.name) when the name property changes, and remove the LiveData<User>.
Make User extend BaseObservable, and put #Bindable annotation on the property getters, and remove LiveData<User>.
Make User properties val, and to make a modification, create a new User instance with the changed properties, and set it as value of the MutableLiveData<User>.
Ditch SelectedUser entirely, and replace it with two ObservableFields, one for selectedId: ObservableInt, and one for selectedName: ObservableField<String>. Now you can modify the values in place and also create bindings against it from databinding.
Remove databinding and use viewbinding instead, now you don't have to worry about notifying the databinding framework of property changes. 😏
Should data class re-expose its fields as LiveData too?
No

Parcelables don't support default values. Android navigation deeplink argument

During the implementation of the passing parameter solution, in navigation between modules, I came across a serialization error. Deeplinks, as far as I know, accepts custom argument types, which are Parcelables or Serializable.
Im using newest version of navigation 2.2.0
Error message:
java.lang.UnsupportedOperationException: Parcelables don't support default values.
Am I doing something wrong or this is still under development?
Here is short example:
<fragment
android:id="#+id/sampleFragment"
android:name="com.testapp.app.samples.navigation.SampleFragment"
android:label="SampleFragment">
<argument
android:name="Args"
app:argType="com.testapp.navigation.SampleArgs" />
<deepLink app:uri="app://app/samples/navigation/SampleFragment?Args={Args}"/>
</fragment>
#Parcelize
#Keep data class SampleArgs(
val text: String
) : NavArgs, Parcelable
val x = SampleArgs("TEST")
val uri = Uri.parse("app://app/samples/navigation/SampleFragment?Args=$x")
navController.navigate(uri)
I found something similar here Android Parcelable don't support default values App Crash
It's my first post on stack, so please be gentle :)
EDIT
Here is answer:
https://issuetracker.google.com/issues/148523779
Parcelables currently don't support default values so you need to pass your object as String value. Yes it is a work around.. So instead of passing object itself as Parcelize object we can turn that object into JSON (String) and pass it through navigation and then parse that JSON back to Object at destination. You can use GSON for object to json-string conversion.

How to change a specific variable from data class in Kotlin?

I have a data class named "DATA" then I made an ArrayList variable like this:
var listData : ArrayList<DATA>
but I need to change a variable in a certain position but I don't know how to change it. If in Java we can just use setter and getter. How to change a specific variable form data class?
First (sorry I have to stay this) classes shouldn't be all caps :-) Usually, in naming conventions, this is applied to constant values. Classes are named with the first letter as capital only (just a recommendation if you are ever going to apply for pro android dev position.). Second, data classes are like much simpler Java's AutoValue generated objects. Their values are immutable on purpose. You can't change a value inside a data class. It is not possible in Kotlin. If there are changes needed in values inside data classes, they usually happen while mapping to another data class. Let's say when mapping a network deserialised data class object to domain layer data class object. (DataResponse -> Data). So if you need to change it, you can allocate the value of the data class to some local variable and then change it, or map it to another data class.
It looks like you want to insert a new data in a specific position in the array?
Given you have your data class defined like this:
data class MyDataClass(var listData : ArrayList<Data>)
class Data
You just need to:
val myDataClass = MyDataClass(arrayListOf())
val someNewData = Data()
val index = 0
myDataClass.listData[index] = someNewData
Try this,
var dataObject = DATA();
dataObject.listData[index] = "Your data";

How to pass not basic data to activity on creation?

In every example I saw, the data is somehow synonymous to basic (raw) data -- ints, chars, array of bools, and so on -- this is too limiting for me, because I would like to pass a regular object.
So how to pass any data to activity, like for example, instance of MyClass?
I checked the Intent.putExtra -- all I found was basic types + Bundle, but Bundle itself also handles only basic types.
There are several way to do it as described in android guide faq.
I think that in your case static variables could help most.
You could also implement Application and use it to share your data between Activities.
Here is short tutorial on that.
In every example I saw, the data is somehow synonymous to POJO data -- this is too limiting for me, because I would like to pass a regular object (not int, or string, or array of bools).
POJO = Plain Ol' Java Object = "regular object (not int, or string, or array of bools)".
So how to pass any data to activity, like for example, instance of MyClass?
Make it Parcelable.

Categories

Resources