A summary of what I wanted to do: I want to create an app with tabs but I'll let the users have a setting where they can choose which couple of tabs do they want to use and in what order would they be.
So I thought I would create an ArrayList of all the possible Fragments and make a new ArrayList based on the user's settings. Now, I created a data class to store some details related to the Fragments, which I also set as the type for the ArrayList, and realized that I can't just pass a Fragment as an argument. Passing a regular Fragment makes Android Studio complain that I would want to use a [FragmentName].Companion instead. Accepting the suggestion makes the parameter exactly equal to [FragmentName].Companion, so I can't use it for other Fragments.
So, how do I do that? Or if you got better ideas on how I could structure the app, please let me know.
I would recommend passing only information about how to create fragments, for example, you can go with creating some FragmentDefinition class that will contain more data required to properly create fragments. for example class name, and bundle with parameters. So you can easily instantiate fragments without needing to know additional details.
data class FragmentDefinition(val className: String, val parameters: Bundle): Parcelable
fun sample(){
val fragmentDefinitions = listOf(
FragmentDefinition("com.sample.FragmentA", Bundle()),
FragmentDefinition("com.sample.FragmentB", Bundle()),
FragmentDefinition("com.sample.FragmentC", Bundle())
)
fragmentDefinitions.forEach { definition ->
val fragment = supportFragmentManager.fragmentFactory.instantiate(this::class.java.classLoader!!,definition.className)
fragment.arguments = definition.parameters
// add your fragment to hierarchy
}
}
Related
I am trying to launch a fragment with object as a parameter in my Espresso test case and I am unable to do so.
val homeFragment= launchFragmentInContainer(themeResId = R.style.AppTheme_NoActionBar)
But I want to send an object to the HomeFragment class.
For eg:
val homeType:HomeType
I want to pass the homeType object to the below line of code, need help with how to do it.
val homeFragment= launchFragmentInContainer(themeResId = R.style.AppTheme_NoActionBar)
I have looked into documentation and stackoverflow link(added below) and I am not able to get the desired solution.
https://developer.android.com/guide/fragments/test
Best practice for instantiating a new Android Fragment
Please help me with this issue.
if you look at the launchFragmentInContainer function documentation, you can see that have parameter fragmentArgs of type Bundle. You can use it to pass arguments to fragment. I don't know how exactly your class HomeType looks like but remember that within Bundle you can pass only custom classes that implements java.io.Serializable or Parcelable.
https://developer.android.com/reference/kotlin/androidx/fragment/app/testing/package-summary#launchFragmentInContainer(android.os.Bundle,kotlin.Int,androidx.lifecycle.Lifecycle.State,androidx.fragment.app.FragmentFactory)
As a newbie in Kotlin, i want to ask a question. Let’s say that i have class Dog like below,
data class Dog(val breed : String, val gender : String, val name : String, val age: Int){}
I want to make 1000 instances of this Dog class and add all of instances in an arrayList and later i will use this list in different activities. Imagine that I have an activity which only shows Labradors . So i have to take that arrayList which contains all of my Dogs and filter the breed according to Labrador and show the user.
As i read in articles i think this is an expensive way but i don’t know what the efficient way is to do this. Cause i will create all instances manually like below.
fun dogMaker(){
val dog1 = Dog("example","example","example",1)
val dog2 = Dog("example","example","example",1)
val dog3 = Dog("example","example","example",1)
val dog4 = Dog("example","example","example",1)
val dog5 = Dog("example","example","example",1)
//... goes on...
}
Could you suggest me an efficient way? Thanks in advance.
As a beginner, don't even worry about trying to optimize this. Just use a FOR loop or initialize the array manually and it'll be fine. If you want to access it in different Activities, just make the ArrayList a global variable by declaring it outside of one of your classes. In your case, this means declaring it above MainActivity.
The better but more involved way to do it is to store all these items in a pre-populated database. That way, you're not creating an ArrayList every time the app runs, you're not taking up memory unnecessarily, and you're only accessing what you need at any given time.
I am writing an application on Kotlin (Android Studio), using jetpack.navigation architecture.
There are two fragments: The first contains a list with class instances, which I display in the RecyclerView, the second for EditText (I fill in the client data). I also use Livedata and ViewModel.
The problem is that when I go to the second fragment, fill in the data and confirm, I go to the 1st fragment. As I understand it, the following lines destroy the old Fragment1, and create a new one. the list on the first fragment is reset to zero (although the list is saved when you rotate the screen and minimize the application).
val client = Clients(id,name,secondName,thirdName, address, creditCard, bankNum)
val action = Fragment2Directions.actionFragment2ToFragment1(client)
findNavController().navigate(action)
I could not find how to solve problem using the navigation component. I will be very grateful.
To pass data between two fragments with jetpack navigation you have to use Safe Args
pass an argument section like
<fragment android:id="#+id/myFragment" >
<argument
android:name="myArg"
app:argType="integer"
android:defaultValue="0" />
</fragment>
add classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" in top level gradle file
and add the plugin apply plugin: "androidx.navigation.safeargs.kotlin"
now send the value like so
override fun onClick(v: View) {
val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
val amount = amountTv.text.toString().toInt()
val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
v.findNavController().navigate(action)
}
and receive it as
val args: ConfirmationFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val tv: TextView = view.findViewById(R.id.textViewAmount)
val amount = args.amount
tv.text = amount.toString()
}
However safeargs works only for primitive types so you have to deconstruct and reconstruct if you're trying to pass Objects
As you are using ViewModels, I recommend that you use a shared ViewModel. The way it works is that multiple Fragments within the same Activity have access to the same ViewModel instance.
There is an example on Android Developers that fits your use case exactly. It shows how to use a shared ViewModel to do this using the master-detail navigation pattern and LiveData. I recommend you take a look at it.
Why not to use Safe Args here: You can try using Safe Args to achieve what you are trying to achieve, but I strongly recommend against it: You would have to deal with somehow using Safe Args to pass your Client objects between the Fragments back and forth (which means either sending each field individually or bundling) and you would have to manually update your LiveData objects - which defeats the purpose of LiveData. Using a shared ViewModel, you do not have to worry about any of that. No sending data back and forth, no taking your Client objects apart or bundling, no manual updating of LiveData objects - you simply access the same LiveData instance from both Fragments through your ViewModel.
Preamble
In trying to get my head around the Kotlin classes to implement Android's ViewModel (and MVVM pattern) as used with Fragments and Activities, it is not clear to me of the trade-offs among the various complex classes especially how they have inherited implicit operations and visible methods (e.g., from the observer objects, managed scope, etc.) versus the old O-O approach of passing list-items and lists between activities in an intent as a bundle or reference, etc.
To illustrate my learning dilemma, I am implementing a crunchy cookie and and a jar to contain the cookies. The cookies can be created, consumed and viewed inside the cookie jar.
Android code tends to be vague on details of classes and the tutorials use deprecated versions, so it is difficult to follow best-practices with the latest version of the Android Architecture Component libraries.
Pseudo Kotlin code:
data class CrunchieCookie : {
var flavor: String?
var calories: String?
var photo: ImageView?
}
class CrunchieCookieViewModel : ViewModel() {
val _crunchieCookie: CrunchieCookie?
val crunchieCookie: CrunchieCookie = _crunchieCookie
}
class CookieJarListViewModel: ViewModel() {
val _cookieJar: MutableLiveData<CrunchieCookie>?
val cookieJar: LiveData<CrunchieCookie> = _cookieJar
}
Purpose
I am expecting to create, update and destroy crunchie-cookies
I am expecting to put crunchie-cookies in a cookie-jar (and take them out)
I am expecting to list all the crunchie-cookies in the cookie-jar in a scrolling ListView
I am expecting to click on a crunchie-cooking in the cookie-jar to open an detail view of the cookie
Finally, storing the cookie-jar in a remote DB, so planning for the local/remote data-source in the future
So, to my way of thinking, the cookie viewmodel will be used in CRUD operations and reused in the detail view from the list model.
MAKING #Tenfour04 's COMMENT AN ANSWER.
Your ViewModel should have a LiveData<List>. The Fragment containing the ListView should observe the LiveData for changes and pass the List along to the ListView when the LiveData value changes. If you're actually just modifying the contents of a MutableList, then you need to set the value of the MutableLiveData to that same list to inform it that there's a change it needs to notify observers about. – Tenfour04 Sep 9 at 0:02
I'm wondering what is a good/best practice to deal with ViewModels and multiple Activities referring to a specific piece of data in my database.
Assuming I have a FriendViewModel which provides access to my friend_database (throught a Repository and a Dao of course) and a RecyclerView displaying all FriendEntities.
If I now want to edit a friend in a different Activity: is it better (or more efficient) to pass one or more FriendEntities (implementing Serializable) to the intent, or should I rather pass the friendId + FriendViewModel and retrieve the FriendEntity from the friend_database using the passed FriendViewModel and friendId?
As this seems to be a common scenario: is there a best practice for that situation?
We usually have separate ViewModels for different Activities/Fragments unless we have common data we want to handle and that's where SharedViewModel comes into play. In your situation, I don't see using a ShareViewModel necessary at all.
FirstActivity:
ListActivity -> ListViewModel -> FriendRepository -> FriendDao
SecondActivity
EditActivity -> EditViewModel -> FriendRepository -> FriendDao
So the only things shared here are your Repository and FriendDao which wraps around Friend model.
Pass friend id from ListActivity to EditActivity, ask FriendRepository to retrieve that friend by using DAO.
That's the approach I personally use and most of the samples out there will use.