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!!!
Related
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
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
I am struggling to use Live data on an MVVM pattern. The app is supposed to:
Fetch data from an API (which it does correctly)
Store that data in the Live data object from the ViewModel
Then the fragment calls the Observer method to fill the recyclerView.
The problem comes in point 3, it does nothing, and I cannot find the solution.
Here is the relevant code. (If I'm missing something, I will try to answer as quickly as possible)
Main Activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: SharedViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Custom button to fetch data from api and log the Live Data value.
binding.refreshFab.setOnClickListener {
viewModel.fetchPlayerData()
Log.d("gabs", "${viewModel.livePlayerlist.value}")
}
}
}
ViewModel:
class SharedViewModel(app: Application): AndroidViewModel(app) {
// val playerDao = LaRojaDB.getDatabase(app).playerDao()
lateinit var playerList: Players
val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
MutableLiveData<MutableList<Players.PlayersItem>>()
}
fun fetchPlayerData() {
CoroutineScope(Dispatchers.IO).launch {
val response = MyService.getLaRojaService().getAllPlayers()
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
val body = response.body()
if(!body.isNullOrEmpty()){
playerList = body
val playerArrayList = mutableListOf<Players.PlayersItem>()
playerList.forEach {
playerArrayList.add(it)
}
livePlayerlist.value = playerList
}
}
}
}
}
}
The fragment that displays the recycler view: (Fragment is already showing, I set up a textView as a title to make sure since I'm new using fragments as well.)
class PlayerListFragment : Fragment() {
private var _binding: FragmentPlayerListBinding? = null
private val binding get() = _binding!!
private val model: SharedViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentPlayerListBinding.inflate(inflater, container, false)
binding.rvPlayerList.layoutManager = LinearLayoutManager(activity)
----> // This is the observer that does not update the UI** <----
model.livePlayerlist.observe( viewLifecycleOwner, {
binding.rvPlayerList.adapter = PlayerAdapter(it)
})
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_player_list, container, false)
}
}
Thank you all in advance, hope I can finally learn what is causing the issue!
I think you don't need to switch Coroutine contexts. A few changes I'd expect if I were reviewing this code:
This should all be in the same IO context. You then postValue to your liveData.
fun fetchPlayerData() {
viewModelScope.launch(Dispatchers.IO) {
val xx = api.fetch()
...
_playerState.postValue(xx) //see below
}
}
Additionally, it's preferred not to expose mutable state, so your ViewModel should not expose the MutableLiveData (which shouldn't really be lazy). But it's also better to encapsulate the state in a sealed class:
//delete this
val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
MutableLiveData<MutableList<Players.PlayersItem>>()
}
Should be: (names are just pseudo code, I have no idea what this code is about)
sealed class PlayerDataState {
data class ListAvailable(data: List<Players.PlayersItem>>): PlayerDataState
object Loading(): PlayerDataState
}
And your new LiveData:
private val _playerState = MutableLiveData<PlayerDataState>()
val playerState: LiveData<PlayerDataState>() get() = _playerState
Finally when observing from the UI, you just...
model.playerState.observe(viewLifecycleOwner, {
when (it) {
is Loading -> ...
is ListAvailable -> binding.rvPlayerList.adapter = PlayerAdapter(it.data)
}
}
Good day, y'all!
Today I was doing the Google Codelabs for Kotlin. I'm trying to use findViewById() in a function but it shows as an unresolved reference. I bet it's a fairly easy problem but I can't figure it out. this is my code
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.done_button).setOnClickListener { addNickname(it) }
}
}
private fun addNickname(view: View) {
val editText = findViewById<EditText>(R.id.nickname_edit)
val nicknameTextView = findViewById<TextView>(R.id.nickname_text)
nicknameTextView.text = editText.text
nicknameTextView.visibility = View.VISIBLE
editText.visibility = View.GONE
view.visibility = View.GONE
}
When I try to assign the editText and the nickmaneTextView vals to the views I use findViewById() but it shows as unresolved reverence.
it's quite basic so I don't know where is the issue
I'll be very grateful for your help
Edit:
if I use
val editText = view.findViewById<EditText>(R.id.nickname_edit)
val nicknameTextView = view.findViewById<TextView>(R.id.nickname_text)
I get an error saying that the nicknameTextView is null and it crashes
You should add view
val editText = view.findViewById<EditText>(R.id.nickname_edit)
val nicknameTextView = view.findViewById<TextView>(R.id.nickname_text)
Ok I figured it out, the fun must be created inside the
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
'here'
}
I knew it was a dumb problem :(
Thank you for your help
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?"