Passing data from one fragment to another (Kotlin) - android-studio

I have a project with 2 fragments. I am looking to pass an iterable from the first fragment to the second. Using navArgs is not an option, since it makes the program crash. Bundle seems to only work with primary data types. Is there a way to go about it, without using some super hacky solution, like passing a string of all the data separated by commas as a string?

The modern way to do this is with a ViewModel (here and here or with the FragmentResult API (last link). Otherwise you're looking at doing it manually through the parent Activity - call a function on the Activity that passes your data to the other Fragment, that kind of thing.
If these Fragments are in separate Activities then you're looking at making your data Parcelable so it can go in a Bundle, or serialisation (e.g. the Kotlin Serialization library) so you can put it in a Bundle as a String, or persist it on disk so you can load it from the next Activity. Serialisation libraries are a robust way of turning objects and data into a stream of text (and other formats if you like) but there's nothing wrong with a String and some separator character if it's all you need, e.g. storing a list of indices or IDs

You can use a shared view model.
In your first fragment:
<code>class FirstFragment : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java)
viewModel.setData(yourIterable)
}
}
</code>
In your second fragment:
<code>class SecondFragment : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java)
val data = viewModel.getData()
}
}
</code>
And your <code>SharedViewModel</code>:
<code>class SharedViewModel : ViewModel() {
private val _data

Related

how to create class of findViewById?

I am a beginner ,https://i.stack.imgur.com/k8z9T.jpgin the android studio whenever I try to use findViewById.It shows an error and they ask me to create its variable but I don't know how to create it . Please tell me, I am stuck here.
You just created a function outside of your MainActivity. You have to create it inside of your activity. According to your screenshot, you just try to create a Top-level Function not a Function because it's outside of your activity. When you need to create a Function you have to create that inside your activity. Keep your eyes on Curley Brackets {}.
See the explanation to understand.
In your screenshot
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
} /* Your activity end's on here*/
private fun addNickName(view: View) {
// Your instance
// val editText = findViewById<EditText>(R.id.nickName_edit)
}
So the answer is
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
private fun addNickName(view: View) {
// Your instance
// val editText = findViewById<EditText>(R.id.nickName_edit)
}
} /* Your activity end's on here*/
Hope you understand!. Thank you

How to access packagemanager in viewmodel

I was using fragment and now I attached ViewModel to it and was transferring code to ViewModel and activity?.packageManager?.getPackageInfo(uri, PackageManager.GET_ACTIVITIES) this line shows an error. How can I access package manager in ViewModel?
On way is to extend AndroidViewModel instead of ViewModel as:
class MyFragmentViewModel(application: Application) : AndroidViewModel(application) {
...
Now you can call:
application.packageManager?.getPackageInfo(uri, PackageManager.GET_ACTIVITIES)
Theoretically if you are implementing MVVM pattern (I see you implementing ViewModel), android.* layer should be handled in the View, Activities/Contexts shouldn't be managed in the ViewModel to avoid Memory Leaks. Even though, depending on the project context, of course this rule doesn't apply to every single project context, I think the best approach would be (if not Dependency Injection is been used) to have an Application Provider.
Create an object:
object ApplicationProvider {
#Volatile
lateinit var application: Application
fun initialize(_application: Application) {
if (!::application.isInitialized) {
synchronized(this) {
application = _application
}
}
}
}
In your MainActivity, initialise the ApplicationProvider as the following:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ApplicationProvider.initialize(this.application)
}
Now you can access the ApplicationContext in your whole project where is needed with:
ApplicationProvider.application.applicationContext
Remember to not assign ApplicationContext to static fields (as a val e.g.) to avoid Memory Leaks.
PD: I'm not very fan of AndroidViewModel, but I guess it is a good solution as well, as a colleague mentioned before :D

Kotlin Android - saving mutableMap in savedInstanceState?

Is there a way to save map/mutableMap into savedInstanceState?
other than using ViewModel functions. I'm curious to know his point...
thanks in advance
I worked on it. wonder why i didn't find anyone asking about this before, probably there are better ways. but in case that someone needs to know how this is done. i did it this way...
i used onSaveInstanceState to check if the map was initialized, then proceeded
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
if (::answersMap.isInitialized){
val keys = (answersMap.keys).toIntArray()
val values = (answersMap.values).toBooleanArray()
savedInstanceState.putIntArray(KEYS, keys)
savedInstanceState.putBooleanArray(VALUE, values)
}
}
also on the onCreate function I retrieved the arrays and created the mutablemap i needed
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val keysArray = savedInstanceState?.getIntArray(KEYS) ?: IntArray(0)
val valuesArray = savedInstanceState?.getBooleanArray(VALUE) ?: BooleanArray(0)
answersMap = mutableMapOf<Int, Boolean>().apply {
for (i in keysArray.indices) this [keysArray[i]] = valuesArray[i]
}
in case you were wondering about the answerMap type
private lateinit var answersMap : MutableMap<Int, Boolean>
IT WORKED!!!

How to update the listview from another thread with kotlin

I'm learning Android studio with Kotlin, I have setup one listview in Oncreate, it list out user data, then I have another function onOptionItemSelected which add/delete user data item, the problem is : after I add/delete from another function, the data on listview cannot be updated:
Here is my code:
class MainActivity : AppCompatActivity() {
private lateinit var listView : ListView
override fun onCreate(savedInstanceState: Bundle?) {
.....
listView = findViewById(R.id.listview_main)
val adapter2 = listViews(this, array_firstname, array_lastname,array_age)
listView.adapter = adapter2
....
override fun onOptionsItemSelected(item: MenuItem): Boolean {
........
GlobalScope.launch {
db1.UsersDao().insertAll(User_tb(0,firstName, lastName, userAge))
(listView.adapter as listViews).notifyDataSetChanged()
}
With this, I got error "Only the original thread that created a view hierarchy can touch its views."
I searched internet and found I need to use runOnUiThread, then I below part under "listView.adapter = adapter2", but it still does not work:
Thread(Runnable {
this#MainActivity.runOnUiThread(java.lang.Runnable {
(listView.adapter as listViews).notifyDataSetChanged()
})
}).start()
I guess I did not understand runOnUiThread correctly but cannot figure out how, could somebody help?
Thanks!
You don't need to mess with Threads directly since you're using coroutines.
Replace this:
GlobalScope.launch {
db1.UsersDao().insertAll(User_tb(0,firstName, lastName, userAge))
(listView.adapter as listViews).notifyDataSetChanged()
}
with this:
lifecycleScope.launch {
withContext(Dispatchers.IO) {
db1.UsersDao().insertAll(User_tb(0,firstName, lastName, userAge))
}
listView.adapter.notifyDataSetChanged() // your cast was unnecessary
}
lifecycleScope runs on the UI thread, except where you wrap the code using withContext to run in the background. So after the withContext block is done, it automatically returns to the main UI thread to run your last line.
This scope also gives you leak protection. If the Activity is closed before the job is done, the coroutine is automatically cancelled and the view elements can be freed to the GC. It won't try to run the last line that updates the UI.
Also, a tip: Your class names should start with a capital letter so they are clearly distinguishable from variable/property names. And they should be more descriptive. For example, I would change the listviews class name to something like UserListAdapter.

"lateinit" or "by lazy" when defining global android.widget var/val

When defining a global android.widget variable, e.g. TextView, is it preferable to use lateinit or by lazy? I initially thought using by lazy would be preferred as its immutable but I'm not entirely sure.
by lazy example:
class MainActivity: AppCompatActivity() {
val helloWorldTextView by lazy { findViewById(R.id.helloWorldTextView) as TextView }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
updateTextView(helloWorldTextView)
}
fun updateTextView(tv: TextView?) {
tv?.setText("Hello?")
}
}
lateinit example:
class MainActivity: AppCompatActivity() {
lateinit var helloWorldTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
helloWorldTextView = findViewById(R.id.helloWorldTextView) as TextView
updateTextView(helloWorldTextView)
}
fun updateTextView(tv: TextView?) {
tv?.setText("Hello?")
}
}
Are there any benefits of using one over the other when defining a global android.widget var/val? Are there any pitfalls with using by lazy to define a android.widget val? Is the decision just based on whether you want a mutable or immutable value?
There's one pitfall with by lazy. The widget property would be read-only and therefore technically final (in Java terms). But there's no documented guarantee that onCreate() is called only once for an instance. Also findViewById() could return null.
So using lateinit is preferable and you'll get an exception to tell you if the val was used before onCreate().
A third possibility would be Android synthetic properties. Then you don't need to worry about the variables at all.
import kotlinx.android.synthetic.main.activity_main.*
helloWorldTextView.text = "Hello?"

Resources