How to save app data in Android after app shutdown using onSaveInstanceState()? - android

I am trying simulate app shut down using ADB. I am storing the data in a bundle in onSaveInstanceState() so that I'm able to get the data back once I get back to the app. But I'm unable to get the data back.
Here's the code I'm using (It's from one of Google's codelabs)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dessertTimer = DessertTimer(this.lifecycle)
if(savedInstanceState != null)
{
revenue = savedInstanceState.getInt(KEY_REVENUE,0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD,0)
dessertTimer.secondsCount = savedInstanceState.getInt(KEY_TIMER_SECONDS,0)
}
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.dessertButton.setOnClickListener {
onDessertClicked()
}
binding.revenue = revenue
binding.amountSold = dessertsSold
binding.dessertButton.setImageResource(currentDessert.imageId)
}
This is the code in onSaveInstanceState()
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState!!.putInt(KEY_REVENUE,revenue)
outState!!.putInt(KEY_DESSERT_SOLD,dessertsSold)
outState!!.putInt(KEY_TIMER_SECONDS,dessertTimer.secondsCount)
}

As #Beko has said, don't use onSaveInstaceState to save something you'll need after an app shutdown. It can not be recovered.
You have two solutions:
Use SharedPreferences.Editor to store your data. SharedPreference.Editor can be got through SharedPreferences#edit;
Or store the data you need to be saved in a database and fetch it later.

Related

How to save variable data in Kotlin

I'm trying to make this so that is saves every time I close the app. The tutorials I found are not clear for me, can anyone please assist me? If needed, here's how my variable I want saved is defined:
private var coins = 0
Here is the GitHub link if needed, which goes straight to MainActivity.
You can use shared preferences to save and retrieve simple data
save data
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
putInt("MY_COINS", coins)
apply()
}
Read data
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val coins = sharedPref.getInt("MY_COINS"), defaultValue)
You can use Shared Preferences for state management or save data in key-value pair.
lateinit var shared : SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_spactivity)
shared = getSharedPreferences("CoinsDB" , Context.MODE_PRIVATE)
var coins = 0
For Save
val edit = shared.edit()
edit.putInt("coins" , coins )
edit.apply()
For retrieve or read
val coinsValue = shared.getInt("coins")
I recommend using storage in files if that isn't a problem for you.

When I delete a data in firebase, it is deleted from the database, but it is not deleted from the list without going to another activity

I'm listing Firebase user's favorite words. When the user presses the favButton, it will remove the favorite word. The favorite word is deleted in the database. But the favorite list is not updated immediately and the deleted word is still in the list. When I come back from another activity, it is deleted. How can I solve this? The place where I deleted the favorite word is in the adapter.
class DeleteFavorite: AppCompatActivity() {
private lateinit var database: DatabaseReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
database = Firebase.database.reference
}
fun deleteFav(keyKelime: String?) {
val database = FirebaseDatabase.getInstance()
val userId = FirebaseAuth.getInstance().currentUser?.uid
val refFavori = database.getReference("/users/${userId}/favorites/${keyKelime}")
refFavori.removeValue().addOnSuccessListener {
Log.e("unfav","Fav geri alındı")
}
}
My adapter
holder.favButton.setOnClickListener {
if (!isClicked) {
holder.favButton.setBackgroundResource(R.drawable.ic_favorite_border)
val deleteFavorite = DeleteFavorite()
deleteFavorite.deleteFav(kelime.keyKelime)
} else {
isClicked = true
val detayActivity = DetayActivity()
detayActivity.writeNewFav(kelime?.keyKelime.toString())
holder.favButton.setBackgroundResource(R.drawable.ic_favorite_dark)
}
}
And my FavoriteWords
fun tumFavoriler(kelimelerListe: ArrayList<Kelimeler>, rvFav:RecyclerView){
adapter = KelimelerAdapter(layoutInflater.context,newKelimelerList)//bura kelime istiyoo
rvFav.adapter = adapter
refFavoriler.get().addOnSuccessListener {
for( c in it.children){
val favKelimeId = c.child("kelime_id").value
kelimelerListe.forEach {
if (it.kelime_id == favKelimeId){
newKelimelerList.add(it)
Log.e("it","$newKelimelerList")
}
}
}
adapter.notifyDataSetChanged()
}.addOnFailureListener{
}
That's because you use get() to read the data from the database, which (as this documentation says) reads the value only once.
If you want your app to respond to changes in the database, use a realtime listener as shown in the documentation section on reading data with a realtime listener.
You could listen to changes within the database as Frank mentioned, but you might not be interested in real-time updates as managing changing data can be difficult.
As an alternative, you can assume the data is successful on completion and update it locally, such as removing the item by index or key properties updating it locally.
This is a practice known as "Positive Assumptions".
This process does use the same logic you would with a snapshot listener, but it won't fire off changes until the user does a specific task.

Saving and retrieving data in Android

private var highScore: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_game)
loadData()
playGame()
}
override fun onDestroy() {
super.onDestroy()
saveData()
}
private fun saveData(){
val sharedPreferences = getSharedPreferences("sharedPrefs", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.apply{
putInt("INTEGER_KEY", highScore)
}.apply()
}
private fun loadData(){
val sharedPreferences = getSharedPreferences("sharedPrefs", Context.MODE_PRIVATE)
val savedInt = sharedPreferences.getInt("INTEGER_KEY", 0)
highScore = savedInt
binding.highScore.text = "Highscore: $savedInt"
}
I've made a simple game and I need to store the the highscore value and retrieve the value when the app is relaunched. I've tried to use sharedPreferences in the given code. But the highscore data is lost when I close the app and relaunch it. How can I save and retrieve the value?
PS: The highscore value is saved/retrieved properly when running the app in Android Studio's emulator. But it doesn't work when I run the app on my phone. It resets to 0 every time I relaunch it.
you are trying to save, when the app is destroyed. This might work sometimes, when onDestroy is actually called, but that does not happen for sure.
Apply is saving the data asynchronous to the disk, which won't happen as you are trying to do it when the app is destroyed. You have to use commit instead of apply to save the data synchronous.
I would recommend to save the data at another point of your app instead of in onDestroy, as this won't be called every time the app is closed/killed.

Looking for a way to persist user data

I need my users to be able to enter an API key in a "Setup" fragment and I need to use this API key in various other places such as other Fragments, Activities, Workers.
It is my understanding so far that getSharedPreferences is designed for this sort of purpose, much like the NSUserDefaults under iOS: save something somewhere, get it elsewhere.
Yet I can't seem to get the getSharedPreferences thing to work, I've had it initialized throughout the app with MainActivity.context but it always loses the data (the API key)
I am using ModelPreferencesManager https://gist.github.com/malwinder-s/bf2292bcdda73d7076fc080c03724e8a
I have an ApplicationState class as follows:
public class ApplicationState : Application() {
companion object {
// ...
lateinit var mContext: Context
var api_key : String = "undefined"
// ...
}
fun save(){
Log.e("ApplicationState", "save")
ModelPreferencesManager.with(mContext)
ModelPreferencesManager.put(api_key , key: "api_key_identifier")
}
fun load(){
Log.e("ApplicationState", "load")
ModelPreferencesManager.with(mContext)
api_key = ModelPreferencesManager.get<String>(key: "api_key_identifier") ?: "not read"
}
}
First, I store the application context on the first Activity (before anything else):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
ApplicationState.mContext = applicationContext
// ...
}
}
I now expect to be able to save the api_key as follows:
ApplicationState.api_key = "blablah" // from some input in a random fragment
ApplicationState.save()
And to load it later:
ApplicationState.load()
var api_key = ApplicationState.api_key // in some activity or random fragment or worker
However it doesn't produce the expected result, the api_key is not saved (or loaded? can't figure out)
I have also tried using a JSON file but still no luck, looks like it either doesn't write/read or just gets deleted for some reason.
I could use a helping hand from someone more experienced as I am new to Android development and can't seem to find my way through these intricacies
I don't know what you are doing with your ModelPreferencesManager.
But this is the standard way to save something in preferences.
val sharedPref = requireContext().getSharedPreferences(keyPrefIdentifier,
Context.MODE_PRIVATE) //get shared preferences
val editor = sharedPref.edit() //make modifications to shared preferences
editor.putString("userApiKeyIdent", "theActualKey")
editor.apply() //save shared preferences persitent.
This is how you read them again.
val sharedPref = requireContext().getSharedPreferences(keyPrefIdentifier,
Context.MODE_PRIVATE)
val apiKey = sharedPref.getString("userApiKeyIdent", "defaultValue")
Edit: You are saving the api key as the identifiery for the preference. But get it as "api key "
It should look like this
fun save(){
Log.e("ApplicationState", "save")
ModelPreferencesManager.with(mContext)
ModelPreferencesManager.put("api_key ", api_key) //like this
}

MVVM Issues with Kotlin - Getting some error with modularization

Objective:
I would like to modularize my code, but I am not well familiarized with Kotlin/Java -- considerable new to both languages, I was coding with React Native.
The following code is how I was making the model-view implementation. If anyone has a better suggestion in how to make a MVVM.
I try to do direct access as in the following code and I try to create instances of the CustomerModel() and CustomerActivity(), but it gives a black blank screen.
This CustomerModel will be responsible to connect to the Customer Database using firebase.
The CustomerActivity will ask to save info, load info and it will display infos using result form database consulting.
There will be other activities accessing CustomerModel (such settings activity, list view activity, ...)
How can I make this code working and better to be handling all Database stuff outside the View?
Issues:
The hasInfo is not being update. It is always false, and checking the Database is true for my test user
I received this error, I must be doing some mistake, but can't find out what:
java.lang.NullPointerException: You cannot start a load on a not yet
attached View or a Fragment where getActivity() returns null (which
usually occurs when getActivity() is called before the Fragment is
attached or after the Fragment is destroyed).
Apparently is happening on tring to update the TextView and the ImageView, maybe there is not created yet(my guess)
CustomerModel.kt (Model)
private lateinit var mCustomerDatabase: DatabaseReference
internal fun getUserInfo(uid: String) {
mUserDatabase = FirebaseDatabase.getInstance().reference.child("Users").child("Customers").child(uid)
mCustomerDatabase.addValueEventListener(object: ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists() && dataSnapshot.childrenCount > 0) {
val map: (Map < String, Any > ) = dataSnapshot.value as(Map < String, Any > )
if (map["profileImageUrl"] != null) {
Glide.with(CustomerActivity().application).load(map["profileImageUrl"].toString()).into(CustomerActivity().mNavigationHeaderImage)
}
if (map["name"] != null) {
CustomerActivity().mNavigationHeaderText.text = map["name"].toString()
}
if (map["hasCarInfo"] != null) {
CustomerActivity().hasInfo = map["hasInfo"].toString().toBoolean()
}
}
}
}
CustomerActivity.kt (View and Activity)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_activity)
(...)
// load all UI settings
handleUI()
// Update User Info
CustomerDatabase().getUserInfo(mAuth.currentUser!!.uid)
}
override fun onStart(savedInstanceState: Bundle?) {
super.onStart()
(...)
// Update User Info
CustomerDatabase().getUserInfo(mAuth.currentUser!!.uid)
}
mNavigationHeaderImage is imported from xml (ImageView)
mNavigationHeaderText is imported from xml (TextView)
hasInfo is a var that is used to check if the user has info or need to fetch by default is false
Your problem is that in your getUserInfo you try to update some text fileds of your activity using following notion
CustomerActivity().mNavigationHeaderText.text = map["name"].toString()
Problematic thing is that you are trying to create instance of your activity using CustomerActivity(). This is wrong, because in Android Instance of activities are created by the Android SDK.
So how do you use instance of your activity?
As you probably know activities has a certain Lifecycle, So after creating instance of your activity and initialization, Android calls lifecycle functions such as onCreate and onRestart, in those functions this refers to your activity's instance.
Ultimate solution to your problem is the LiveData, read this to learn about LiveData.
Following are two alternate solutions, both of these will work but if you can configure your project to use LiveData then avoid the following.
To solve your problem, a simple yet wrong thing would be to do the following, here you pass the instance of activity as a parameter to getUserInfo
CustomerDatabase().getUserInfo(this, mAuth.currentUser!!.uid)
Second approach would be to use callback interface. so in your activity define an interface as following
interface Callback {
fun onFinished(map: Map <String, Any> )
}
Now modify your activity to implement this interface, so in your activity you will have a function as following
onFinished(map: Map <String, Any> ){
// use map to set your data
}
After this modify your getUserInfo to take a parameter of type Callback, and this callback will be called by getUserInfo when it gets data.
internal fun getUserInfo(uid: String, callback: Callback) {
mUserDatabase = FirebaseDatabase.getInstance().reference.child("Users").child("Customers").child(uid)
mCustomerDatabase.addValueEventListener(object: ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists() && dataSnapshot.childrenCount > 0) {
val map: (Map < String, Any > ) = dataSnapshot.value as(Map < String, Any > )
/** When data is available, pass it to activity by calling the callback */
callback.onFinished(map);
}
}
}

Categories

Resources