I think I'm doing it wrong but this is my situation.
I'm getting json data inside a fragment then process it with Gson to a data class and display it. What I need is to use this data again inside another fragment in a custom spinner adapter which is ready.
As much as I understand it's impossible to pass objects so how can I do this !?
I have tried to use bundle and it didn't work
the onResponse method (First fragment)
override fun onResponse(call: Call?, response: Response?) {
val jsonString = response?.body()?.string()
val gson = GsonBuilder().create()
val data = gson.fromJson(jsonString,currancyModel::class.java)
countryData = data
activity?.runOnUiThread {
rootView.recyclerView.adapter = CountryAdapter(data)
}
}
the data class
data class currancyModel(val items: List<Item>)
data class Item(val countray :String,val rate:String)
the getView in the custom spinner adapter inside the second fragment
(I need my data here)
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
val view = inflater.inflate(R.layout.custome_spinner,null)
val img = view.findViewById<View>(R.id.spinner_image) as ImageView
val countary_name = view.findViewById<View>(R.id.spinner_country) as TextView
img.setImageResource(R.drawable.us)
countary_name.setText(country!![p0].countray)
return view
}
Do you display two fragments in your Activity simultaneously? If so, you can pass the data through it. Or implement some interface/observable/livedata to pass the data between the fragments.
If Fragment A fetches the data and then you change Fragment A to Fragment B, make your data classes Parcelable and then pass it as arguments when creating Fragment B:
companion object {
fun newInstance(yourData : DataClass) = FragmentB().apply { arguments = Bundle().apply { putParcelable("dataKey",yourData) }
}
Note: you can annotate your data class with #Parcelize. Compiler will then generate all Parcelable methods and factory class for you.
After you passed the data to Fragment B on creation, retrieve it with, for example:
val data: YourData by lazy { arguments?.getParcelable("dataKey") as YourData }
Indeed it is possible to pass objects from one fragment to another given your object class should implement Parcelable. And passing the object through the bundle by calling putParcelable on the bundle object.
1. class CurrancyModel : Parcelable {
//Implement Parcelable
}
And pass it between the fragments via Bundle.
2.var fragment = YourFragment().apply {
arguments = Bundle().apply{ putParcelable("dataKey",yourData) }
}
An idea could be to use a singleton data class, with a HashMap<String, Object> having a key with some kind of ID you create yourself, and then the value being the object you want to be able to retrieve. So in the onResponse you will add your data to the HashMap in the dataclass and then just retrieve it from the other class.
Related
The data in the recyclerview in my fragment uses the api of the website. But the data is lost when the bottom navigation is switched. But the data structure is not a simple int or string. How should I write it in onSaveInstanceState to store it. And how to make him restore the data type of LiveData<List> normally?
The data of the recyclerview looks like in the viewmodel.
private val _photos = MutableLiveData<List<SportData>>()
val photos: LiveData<List<SportData>> get() = _photos
data class
#Parcelize
data class SportData (
val GymID:Int,
val Photo1:String,
val Name:String,
val Address:String,
val OperationTel:String,
val OpenState:String,
val GymFuncList:String
): Parcelable
I try to save the data in onDestroyView() and fetch it in onViewCreated. It fails with null.
override fun onDestroyView() {
photos2=viewModel.photos
super.onDestroyView()
Log.d("aaa","destroyVIEW and ${photos2}")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d("aaa","=viewCreate and ${photos2}")
if (photos2!=null){
viewModel.saveData(photos2!!)
}
viewmodel
fun saveData(savePhoto:LiveData<List<SportData>>){
_photos=savePhoto as MutableLiveData<List<SportData>>
}
hello can you help me? thanks
Your data is already at ViewModel. You don't need to save it. Since data in your viewModel and viewModel lives while your aplication lives, you'll not lose it.
What might be happening is a reload when you go back to your list's fragment, right?
You are calling a viewModel method from your fragment. This method does the request.
What you need to do is to make sure your fragment won't call it if it doesn't need.
What you need to do is:
if (savedBundleState == null) { //Read this as android creating this frag for the very first time
//Here you call viewmodel method that does the request.
}
This is part 1
Since you are using Navigation Component, you'll need to setup it to avoid new fragments killing older fragments.
I have a sealed class like so:
sealed class SealedClass {
object Object1 : SealedClass()
object Object2 : SealedClass()
object Object3 : SealedClass()
data class DataClass(val sealedClass: SealedClass, val anotherDataType: AnotherDataType? = null)
}
I would like to pass my data class in a Bundle like we normally pass values to a new fragment like so:
#JvmStatic
fun newInstance(dataClass: DataClass): Fragment {
val fragment = Fragment()
val args = Bundle(1)
args.putParcelable("DATA_CLASS", dataClass)
fragment.arguments = args
return fragment
}
I'm not sure how to go about this. So far what I've read is that people use an #Parcelize annotation, which is an experimental feature of Kotlin that I'm trying to avoid. Another approach is to extend the data class by Parcelable and implement the Parcelable methods, but since I use custom classes as parameters in the DataClass (for instance, SealedClass), I don't know how to read/write those values inside Parcelable implementation. Is this not a right approach to go about it?
I think this can be simpler now with recent Kotlin using Parcelable:
#Parcelize
data class TimeSeries(
val sourceInfo: SourceInfo? = null,
val variable: Variable? = null,
val values: List<Value_>? = null,
val name: String? = null
) : Parcelable
Then pass it in your bundle:
val intent = Intent(context, DetailsActivity::class.java).apply {
putExtra(MY_DATA, mydata[position])
}
context.startActivity(intent)
Then bring it in through your bundle:
mydata = intent?.getParcelableExtra<TimeSeries>(MY_DATA)
If you want instead to pass a Bundle you can also just use bundleOf(MY_DATA to mydata[position]) when putting the Extra, and intent?.getBundleExtra(MY_DATA)?.getParcelable<TimeSeries>(MY_DATA) when getting it, but looks like adding another layer.
If you want to transform the sealed class as parcelable, you can do the following:
sealed class SealedClass : Parcelable {
#Parcelize
object Object1 : SealedClass()
#Parcelize
object Object2 : SealedClass()
#Parcelize
object Object3 : SealedClass()
#Parcelize
data class DataClass(val sealedClass: SealedClass, val anotherDataType: AnotherDataType? = null) : SealedClass()
}
Serializable while using reflection and causing a bit more garbage collection, is easier to implement.
I find it easiest to use GSON.
https://github.com/google/gson
First, add it to your data class like this:
data class TimeSeries(
#SerializedName("sourceInfo")
val sourceInfo: SourceInfo? = null,
#SerializedName("variable")
val variable: Variable? = null,
#SerializedName("values")
val values: List<Value_>? = null,
#SerializedName("name")
val name: String? = null
) : Serializable
Then pass it in your bundle:
val intent = Intent(context, DetailsActivity::class.java).apply {
putExtra(MY_DATA, Gson().toJson(mydata[position]))
}
context.startActivity(intent)
Then bring it in through your bundle:
mydata = Gson().fromJson(intent?.extras?.getString(MY_DATA), TimeSeries::class.java)
I've tried extracting the value into a base class and having the ViewModels extend it. When I do that, however, the Observer isn't sticking to the LiveData. For instance, when I have a parent class with LiveData:
class Base : ViewModel() {
private val _ data = MutableLiveData()
val data: LiveData = _data
fun someEvent(foo: Foo) { // update _data }
}
class Derived : Base()
class Derived1 : Base()
Then get one of those ViewModels and observe data:
class Frag : Fragment {
onViewCreated() {
// get Derived, ViewModelProviders.of ...etc
derived.data.observe { // Doesn't observe changes }
}
}
Calling Base.someEvent(foo) doesn't notify the LiveData in the Fragment.
I want to avoid getting a reference to both subclasses and invoking someEvent on each. One thing to note is that I'm using a single Activity approach and all ViewModels are Activity scoped.
class Derived : Base()
and
class Derived1 : Base()
have their own instance of:
private val _ data = MutableLiveData()
val data: LiveData = _data
that means you need to
derived.data.observe { // do something }
derived1.data.observer { // do something }
derived.someEvent(someFoo)
derived1.someEvent(someFoo)
You are trying to achieve something in a wrong way.
I've been looking for some time to android architechture components and lately to the Navigation component.
I'm trying to pass as a parameter an object, so the next fragment can retrieve that data, but to do so I'm required to do one of two things:
Pass it through a Bundle, which will make me implement the Parcelable interface to that object.
Use the "Safeargs" plugin which I've tried and it looks like behind the hood makes use of Bundles and requires the implementation of the Parcelable interface anyway.
The thing about these options is that I've read that Parcelable makes use of reflection and it can get quite expensive regarding time
I have also tried to build a "SharedMasterDetailsViewModel" but with no luck since the observable callbacks are not being performed on my newly created Fragment. (I think LiveData performs the callback before my fragment is created)
Here's some code about how I've tried to approach this
SharedSessionViewModel
class SessionSharedViewModel : ViewModel() {
var sharedSession: LiveData<Session> = MutableLiveData()
private set
fun setSession(data: Session) {
val casted = this.sharedSession as MutableLiveData<Session>
casted.postValue(data)
}
}
MasterFragment
override fun onItemClicked(item: Session) {
sessionSharedViewModel.setSession(item) // Item is a complex object of mine
this#HomeFragment.findNavController().navigate(R.id.sessionDetailsFragment)
}
DetailsFragment
class SessionDetailsFragment : Fragment() {
companion object {
fun newInstance() = SessionDetailsFragment()
}
private lateinit var sharedViewModel: SessionSharedViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.session_details_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
Log.d("SESSIONDETAILS","created!")
super.onActivityCreated(savedInstanceState)
sharedViewModel = ViewModelProviders.of(this).get(SessionSharedViewModel::class.java)
sharedViewModel.sharedSession.observe({this.lifecycle},{ handleUI(it!!)})
}
fun handleUI(sharedSession: Session) {
Toast.makeText(activity, "This is inside new activity: ${sharedSession.title()}", Toast.LENGTH_SHORT)
}
}
My last hope is to serialize my object into a JSON string and reparse that object on the onCreateActivity lyfecycle hook of my Detail fragment but I feel like that is not the proper solution.
In the worst case scenerio I would just pass the id of the object and re-fetch it from the network, but since I already have the info I want to show I'd like to pass it as a parameter.
TL; DR
You can't.
Actual explanation
First thing; the following statement
The thing about these options is that I've read that Parcelable makes use of reflection and it can get quite expensive regarding time.
IS A LIE
Since you implement Parcelable you're just providing methods on how to serialize and deserialize using basic primitive types: IE: writeBytes, readBytes, readInt, writeInt.
Parcelable does NOT use reflection. Serializable does!
While it's true you are forced to use Parcelable jet brains developed a very useful annotation that takes away the pain of having to write the parcelable implementation called #Parcelize.
Sample usage:
#Parcelize
data class User(val username: String, val password: String) : Parcelable
And now you're able to pass instances of the class without writing a single line of Parcelable implementation.
More info here
I have little problem with pass Recyclerview item ID from Activity to ViewModel. I need this ID to edit objects.
Does anyone know how to do it in accordance with the MVVM architecture?
let's try this code, you can pass context object in constructor of ViewModel class and you can also pass binders object.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myAddressActivityBinding= DataBindingUtil.setContentView(this#MyAddressActivity, R.layout.my_address_activity)
mMyAddressViewModel=MyAddressViewModel(this#MyAddressActivity)
myAddressActivityBinding!!.viewModel=mMyAddressViewModel
}
}
here you can find variable or id something like this, this may be your ViewMoidel class in which you are getting context object.
class MyAddressViewModel(val mMyAddressActivity: MyAddressActivity) : BaseObservable(), DeleteAdressCallback {
private val tilEmail = mMyAddressActivity.myAddressActivityBinding!!.tilEmail
}
and possible you have bound your object in XML too by using data