Type mismatch: inferred type is Array<Profile> but Array<(out) Parcelable!>? was expected Navigation graph - kotlin

I am trying to pass an ArrayList<Profile> in a bundle from one fragment to another using the Navigation Graph, but I am getting this error Type mismatch: inferred type is Array<Profile> but Array<(out) Parcelable!>? was expected I have already passed on the navigation the type of argument that I want to pass. What am I missing? Here my code
Code that passes the argument
emptyHomeViewModel.playerByIDLiveData.observe(viewLifecycleOwner) { profile ->
if (profile != null) {
profilesList.add(profile)
bundle = Bundle().apply {
putSerializable("user", profilesList)
}
findNavController().navigate(
R.id.action_emptyHomeFragment_to_selectUserFragment,
bundle
)
Navigation XML for the fragment that will receive
<fragment
android:id="#+id/selectUserFragment"
android:name="com.example.dota2statistics.SelectUserFragment"
android:label="fragment_select_user"
tools:layout="#layout/fragment_select_user" >
<argument
android:name="user"
app:argType="com.example.dota2statistics.data.models.byID.Profile[]" />
</fragment>
Code of the fragment that receives the ArrayList
class SelectUserFragment : Fragment(R.layout.fragment_select_user) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val args : SelectUserFragmentArgs by navArgs()
val profilesList = args.user
Log.i("Profiles", "onViewCreated: ${profilesList[0].personaname} ================")
}

add this plugin
plugins {
id("kotlin-parcelize")
}
then make your class parcelable for example
import kotlinx.parcelize.Parcelize
#Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
Safe args only allows passing Array so before adding bundle we have to convert ArrayList to Array
bundle.putParcelableArray("user", profilesList.toTypedArray())
Then when getting the argument we can convert it back to ArrayList
val list: ArrayList<Profile> = ArrayList(args.user.toList())

Related

Espresso "scrollTo(Matcher<...>)" not working with data bound items

While I can scroll to a specific index of my RecyclerView I cannot scroll to an item that satisfies a given Matcher that relies upon the values of data bound values.
If I hard code the layouts of the items inserted into the RecyclerView with values then the Matcher finds them but if these values are filled through data binding the values the Matcher is comparing are all "empty". This happens even though the pending bindings have all been completed and no amount of waiting (or sleeping) or anything will make them available.
It is also interesting to note that Espresso's "withText" Matcher can 'see' the values when run as a simple assertion but when used as a Matcher in the "scrollTo(...)" method it fails to find them.
It's worth noting that the "scrollTo(...)" method does cause my adapter's "onCreateViewHolder(...)" to be run again but the adapter's "onBindViewHolder(...)" is also run before the checks start and inserting IdlingResource blockers in these methods does not help.
Any idea what is happening here?
Edit: I have made a simple stand alone project that illustrates this issue in an attempt to isolate the problem.
Adding specific code as an example
The "text_row_item"s Layout file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="text"
type="String"
/>
</data>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{text}"
/>
</layout>
My Adapter looks like this:
class CustomAdapter(private val dataSet: List<String>) :
RecyclerView.Adapter<CustomAdapter.CustomViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.context),
R.layout.text_row_item,
viewGroup,
false)
binding.executePendingBindings()
return CustomViewHolder(binding)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(dataSet[position])
}
inner class CustomViewHolder(private val binding: ViewDataBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.setVariable(BR.text, item)
}
}
override fun getItemCount() = dataSet.size
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = CustomAdapter(listOf(
"W00t",
"W01t",
.
.
.
"W41t",
"W42t"
))
}
}
And my actual Espresso test file itself contains:
#Test
fun checkW00_42t() {
withText("W00t").assertAny(isDisplayed())
checkText("W00t")
checkText("W01t")
.
.
.
checkText("W41t")
checkText("W42t")
}
private fun checkText(text: String) {
Espresso.onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(text))))
withText(text).assertAny(isDisplayed())
And this is what this app looks like at the time the test is run:
There are 43 "W00t"s added to the RecyclerView so that it will need to be scrolled to see the last ones however it fails to find even the first one that doesn't require scrolling at all.
It's worth noting that the first check (which doesn't use "scrollTo(...)") passes:
withText("W00t").assertAny(isDisplayed())
However attempting to scroll to the exact same text fails:
checkText("W00t")
It is also worth noting that if instead of using DataBinding one were to just assign the text in onBindViewHolder, as in:
viewHolder.textView.text = dataSet[position]
the scrollTo(...) works.
Oh my goodness, I think I have it!
I had to move executePendingBindings() from onCreateViewHolder(...) to CustomViewHolder():
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(...)
// binding.executePendingBindings() // ** remove here **
return CustomViewHolder(binding)
}
inner class CustomViewHolder(private val binding: ViewDataBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.setVariable(BR.text, item)
binding.executePendingBindings() // ** insert here **
}
}

SavedState module Android Kotlin with View Model - value does not seem to save

I have followed the instructions in Google Codelab about the Saved state module.
My gradle dependency is:
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-rc03"
My View Model factory is:
class MyViewModelFactory(val repository: AppRepository, owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle): T {
return MyViewModel(repository, handle) as T
}
}
My View model:
class MyViewModel constructor(val repository: AppRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
fun getMyParameter(): LiveData<Int?> {
return savedStateHandle.getLiveData(MY_FIELD)
}
fun setMyParameter(val: Int) {
savedStateHandle.set(MY_FIELD, val)
}
}
My Fragment:
class MyFragment : androidx.fragment.app.Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
arguments?.let {
var myField = it.getInt(MY_FIELD)
activitiesViewModel.setMySavedValue(myField ?: 0)
}
}
}
When the app is being used, the live data for the saved state field updates correctly. But as soon as I put the app in background (and I set "don't keep activities" in developer options), and then re open app, the live data shows a value as if it was never set. In other words, the saved state handle seems to forget the field I am trying to save.
Any thoughts?
I had the same problem. It was solved by the fact that I stopped requesting saved values from SavedStateHandle in the onRestoreInstanceState method. This call is correctly used in the onCreate method.

I want to pass data to popup dialogFragment

in kotlin … I want to pass data to a custom popup window which extends dialogFragment so anyone here knows how can I pass data to such a fragment ?.
I have error every time I pass data to the constructor.
Please help.
pass data to the constructor
class PopUpClass : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var v = inflater.inflate(R.layout.poplayout,container,false
return v
}
//tried to pass the data in the constructor and then handle it but did not work
Have a look at the dialog fragment documentation.
You need to create a function getInstance that passes your parameters through a bundle to the fragment. Like this:
static MyDialogFragment newInstance(int num) {
MyDialogFragment f = new MyDialogFragment(); // Supply num input as an argument.
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
Here is how you can do it in Kotlin, first declare a companion object with instance of your fragment class
companion object {
#JvmStatic //This can be avoided if you are in a complete Kotlin project
fun newInstance(content: String): PopUpClass {
val args = Bundle()
args.putString("content", content)
val fragment = PopUpClass()
fragment.arguments = args
return fragment
}
}
Inside onCreate() or onViewCreated() of your fragment you can receive the data like this
val dataPassed: String? = arguments?.getString("content")
Call the newInstance instead of constructor from your parent activity or fragment

Parcelized sealed class with private constructor throws inaccessible error when getting from Intent in new activity but works everywhere else?

I made a special sealed class to validate my nullable URLs so I can safely and easily know it's state (None, Invalid, Valid)
sealed class UrlType : Parcelable {
#Parcelize class Valid private constructor(val url: String) : UrlType() {
companion object : CompanionTest<UrlType.Valid, String>(::Valid)
}
#Parcelize object None : UrlType()
#Parcelize class Invalid(val invalidUrl: String) : UrlType()
companion object {
fun getUrlType(url: String?): UrlType {
return if (url == null) {
UrlType.None
} else if (!url.isValidUrl()) {
UrlType.Invalid(url)
} else {
Valid.create(url)
}
}
}
}
open class CompanionTest<out T, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
fun create(arg1: A): T {
return creator!!(arg1)
}
}
#Parcelize
data class NewsPost(
val id: String,
val title: String,
val description: String?,
val webContentUrl: UrlType,
val imageUrl: UrlType,
val created: String,
val updated: String,
val feeds: List<Feed>
) : Parcelable
When I was first trying out the idea, I had my suspicions that it wouldn't work because of the private constructor and Parcelize but I did some tests and it did work.
I also have a sealed class to indicate a launch type for my activity which can be entered from multiple paths
sealed class NewsLaunchType : Parcelable {
#Parcelize object FeedList : NewsLaunchType()
#Parcelize class FeedDetail(val newsFeedType: NewsFeedType) : NewsLaunchType()
#Parcelize class PostDetail(val newsPost: NewsPost) : NewsLaunchType()
}
When I go to launch my activity with a NewsLaunchType.PostDetail(newsPost) the app is crashing with the following error
fun startActivity(){
val intent = Intent(context, NewsActivity::class.java).apply {
putExtra("key", NewsLaunchType.PostDetail(newsPost))
}
startActivity(intent)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news)
val newsActivityLaunchType = intent.getParcelableExtra<NewsLaunchType>("key")
}
2018-11-08 11:08:33.897 17030-17030/com.something.internal E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.something.internal, PID: 17030
java.lang.IllegalAccessError: Method 'void com.something.somethingkit.helper.UrlType$Valid.<init>(java.lang.String)' is inaccessible to class 'com.something.somethingkit.helper.UrlType$Valid$Creator' (declaration of 'com.something.somethingkit.helper.UrlType$Valid$Creator' appears in /data/app/com.something.internal-QTiupS4yw25rZK5uXP7UCQ==/base.apk:classes2.dex)
at com.something.somethingkit.helper.UrlType$Valid$Creator.createFromParcel(Unknown Source:11)
at android.os.Parcel.readParcelable(Parcel.java:2798)
at com.something.somethingkit.model.news.local.NewsPost$Creator.createFromParcel(Unknown Source:25)
at android.os.Parcel.readParcelable(Parcel.java:2798)
at com.something.screens.news.NewsLaunchType$PostDetail$Creator.createFromParcel(Unknown Source:13)
at android.os.Parcel.readParcelable(Parcel.java:2798)
at android.os.Parcel.readValue(Parcel.java:2692)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3059)
at android.os.BaseBundle.unparcel(BaseBundle.java:257)
at android.os.Bundle.getParcelable(Bundle.java:888)
at android.content.Intent.getParcelableExtra(Intent.java:7734)
at com.something.screens.news.NewsActivity.onCreate(NewsActivity.kt:31)
at android.app.Activity.performCreate(Activity.java:7183)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2908)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3030)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6938)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Now that's not entirely unexpected, except that it does work if I try to pull it out within the same location I created it in
fun startActivity(){
val intent = Intent(context, NewsActivity::class.java).apply {
putExtra("key", NewsLaunchType.PostDetail(newsPost))
}
val test = intent.getParcelableExtra<NewsLaunchType>("key")
startActivity(intent)
}
It also works if I add it to a fragment bundle, and add/replace a new fragment into my activity.
companion object {
fun newInstance(newsPost: NewsPost): NewsDetailFragment {
val bundle = Bundle()
bundle.putParcelable(extraNewsPost, newsPost)
val fragment = NewsDetailFragment()
fragment.arguments = bundle
return fragment
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val newsPost = arguments?.getParcelable<NewsPost>(extraNewsPost)
}
Note - When passing it across activities, there is an extra layer of Sealed Classes going on, but it will crash as well if I simply tried to pass a NewsPost across activities, so the extra layer is not the issue.
This seems like it might be a bug with the Kotlin Parcelize feature.
Is there any reason why passing it across activities would throw an error but passing it between fragments doesn't? If there is a reason... any suggestions on how I could pass this across activities?
The docs clearly state:
The primary constructor should be accessible (non-private)
Not sure why it's working when passing to fragment and not working when passing to activity.
In my case, I solved this issue by making my class constructor internal.

Kotlin - Remove repetitive method calls for fragments

I am trying to remove duplicating methods by creating one singular method that takes params. I have a few methods that do the exact thing where they create an instance of a class, a fragment manager and then shows the fragment. Just want to know how I can shorten the following into one method and just pass in params.
private fun openAboutDialogue() {
//get a fragment manager
val fm = fragmentManager
val abtDialogue = GetStartedFragment()
abtDialogue.show(fm, "About the App")
}
private fun openNewRouteDialogue() {
val confirmNewDialogue = NewRouteFragment()
val fm = fragmentManager
confirmNewDialogue.show(fm, "NewRoute")
}
private fun openEndRouteDialogue() {
val confirmEndDialogue = TrafficDataFragment()
val fm = fragmentManager
confirmEndDialogue.show(fm, "GetTraffic")
}
If I understand this correcly, you simply create something like the following which takes Fragment as an argument:
private fun openDialogue(fragment: Fragment, text: String) =
fragment.show(fragmentManager, text)
Technically you could do
fun AppCompatActivity.openDialogue(fragment: DialogFragment, tag: String) {
fragment.show(supportFragmentManager, tag)
}
But now you have to call it as
openDialogue(GetStartedFragment(), "About the App")
openDialogue(NewRouteFragment(), "NewRoute")
openDialogue(TrafficDataFragment(), "GetTraffic")
If you want to get fancy and hide Fragment class from the caller you can use an enum for selection which can double as a fragment tag as well:
enum class DialogueType{ GET_STARTED, NEW_ROUTE, TRAFFIC,DATA }
private fun openDialogue(type: DialogueType){
val fragment = when(type) {
GET_STARTED -> GetStartedFragment()
NEW_ROUTE -> NewRouteFragment()
TRAFFIC_DATA -> TrafficDataFragment()
}
fragment.show(fragmentManager, type.name)
}