Android: I have made a simple Todo list app using Kotlin. And the theme (Light/Dark) of this app is set to system default theme MODE_NIGHT_FOLLOW_SYSTEM. Now the problem is that when I changed theme to Dark mode(because my emulator's theme was set to Light theme previously), all added Todo items were gone and same when I changed to Light mode again. Then I noticed the size of my ArrayList became 0 (empty).
Here is screenshots to understand it better:
Light Mode
https://i.stack.imgur.com/eWbzO.png
after changing system theme to Dark
Dark Mode
https://i.stack.imgur.com/B9iqg.png
tasks are added to list: ArrayList when saveButton: Textview is clicked
class MainActivity : AppCompatActivity() {
var modified = false
private lateinit var listView: ListView
private lateinit var input: EditText
private lateinit var saveButton: TextView
private lateinit var cancelButton: TextView
private val list = ArrayList<String>()
private lateinit var listAdapter: CustomAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
listView = findViewById(R.id.listView)
input = findViewById(R.id.input)
saveButton = findViewById(R.id.saveBtn)
cancelButton = findViewById(R.id.cancelBtn)
listAdapter = CustomAdapter(this, list)
listView.adapter = listAdapter
val warningToast = Toast.makeText(this,
"The text may be empty or contains only spaces",
Toast.LENGTH_LONG)
saveButton.setOnClickListener {
val text = input.text.toString()
// if text is empty or only contains blank
if (text.isEmpty() || text.isBlank()) {
warningToast.show()
return#setOnClickListener
}
if (!modified) {
// output list size 0 after changing theme
Log.d("MY_DEBUG", "size fo list is : ${list.size}")
list.add(input.text.toString())
} else {
// ...
}
input.text.clear()
listAdapter.notifyDataSetChanged()
}
// ...
}
}
I thought that this is because it triggers a uiMode configuration change when theme mode is changed. So, the activity is recreated automatically. I want to keep added items even after changing theme. Is there any way to prevent loss of added items. Thank you!
Changing theme recreates your activity and it reinitiates your list. It is not because of changing theme itself.
You have to save your data persistently, in a database or in a SharedPreferences instance with your own serialization method (like JSON).
Related
I would like to build an activity, which displays a set of strings in a loop until the user clicks on the screen.
Any suggestions is greatly appreciated.
I am trying to do this in Kotlin, but Java advice is also welcomed.
I have built an activity with TextSwitcher and a variable (isClicked) that indicates whether the user has clicked on the screen. I set up a listener that sets the isClicked when user clicks. Then in a loop I check the value of isClicked and if it is not checked I change the value of the text.
class SelectionActivity : AppCompatActivity() {
var isClicked = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.selection)
val textSwitcher = findViewById<TextSwitcher>(R.id.selectionText)
textSwitcher.setFactory {
val textView = TextView(this)
textView.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
textView.textSize = 32f
textView
}
textSwitcher.setCurrentText("Start")
textSwitcher.setOnClickListener {
isClicked = false;
}
var i = 0;
while (isClicked) {
textSwitcher.setText(i.toString())
i++;
}
}
}
This does not seem to work. The app just hangs and becomes non-responsive. If I understand correctly, it might have something to do with activity class being in the UI thread and while loop being too heavy for it. However, I am not sure how this should be solved.
The step counter code works, but it counts the steps inaccurately (for example I make 10 steps and it shows either 0 or more than 10). It also takes some time to show how many steps were made. The code is written in Android Studio, Java language. Can anyone help to optimize it or tell where is the problem?
Here is the Main activity code
`
package com.example.stepcounter14
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import android.widget.Toast
class MainActivity : AppCompatActivity(), SensorEventListener {
// Added SensorEventListener the MainActivity class
// Implement all the members in the class MainActivity
// after adding SensorEventListener
// we have assigned sensorManger to nullable
private var sensorManager: SensorManager? = null
// Creating a variable which will give the running status
// and initially given the boolean value as false
private var running = false
// Creating a variable which will counts total steps
// and it has been given the value of 0 float
private var totalSteps = 0f
// Creating a variable which counts previous total
// steps and it has also been given the value of 0 float
private var previousTotalSteps = 0f
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadData()
resetSteps()
// Adding a context of SENSOR_SERVICE aas Sensor Manager
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
override fun onResume() {
super.onResume()
running = true
// Returns the number of steps taken by the user since the last reboot while activated
// This sensor requires permission android.permission.ACTIVITY_RECOGNITION.
// So don't forget to add the following permission in AndroidManifest.xml present in manifest folder of the app.
val stepSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
if (stepSensor == null) {
// This will give a toast message to the user if there is no sensor in the device
Toast.makeText(this, "No sensor detected on this device", Toast.LENGTH_SHORT).show()
} else {
// Rate suitable for the user interface
sensorManager?.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI)
}
}
override fun onSensorChanged(event: SensorEvent?) {
// Calling the TextView that we made in activity_main.xml
// by the id given to that TextView
var tv_stepsTaken = findViewById<TextView>(R.id.tv_stepsTaken)
if (running) {
totalSteps = event!!.values[0]
// Current steps are calculated by taking the difference of total steps
// and previous steps
val currentSteps = totalSteps.toInt() - previousTotalSteps.toInt()
// It will show the current steps to the user
tv_stepsTaken.text = ("$currentSteps")
}
}
fun resetSteps() {
var tv_stepsTaken = findViewById<TextView>(R.id.tv_stepsTaken)
tv_stepsTaken.setOnClickListener {
// This will give a toast message if the user want to reset the steps
Toast.makeText(this, "Long tap to reset steps", Toast.LENGTH_SHORT).show()
}
tv_stepsTaken.setOnLongClickListener {
previousTotalSteps = totalSteps
// When the user will click long tap on the screen,
// the steps will be reset to 0
tv_stepsTaken.text = 0.toString()
// This will save the data
saveData()
true
}
}
private fun saveData() {
// Shared Preferences will allow us to save
// and retrieve data in the form of key,value pair.
// In this function we will save data
val sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putFloat("key1", previousTotalSteps)
editor.apply()
}
private fun loadData() {
// In this function we will retrieve data
val sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE)
val savedNumber = sharedPreferences.getFloat("key1", 0f)
// Log.d is used for debugging purposes
Log.d("MainActivity", "$savedNumber")
previousTotalSteps = savedNumber
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// We do not have to write anything in this function for this app
}
}
`
I have a button to delete all data on my activity screen (a chart, database data and a textview) and to display a toast.
Pressing the button does everything apart from getting rid of the textview, which only happens upon a second press. The code is in onCreate. Why is this happening and how can I fix it? Thank you :)
findViewById<ImageButton>(R.id.delete_btn).setOnClickListener {
pieChart.visibility = View.GONE
textView.visibility = View.GONE
appsViewModel.removeAll()
Toast.makeText(this, "Successfully deleted all", Toast.LENGTH_SHORT).show()
}
As #mayurgajra made me realise, some other code interfered with the changes in the textView. I managed to fix this with a boolean. I set it to true within the ImageButton event, and now I only modify the textview visibility in my other code if the boolean is false.
private var deleted: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
...
findViewById<ImageButton>(R.id.delete_btn).setOnClickListener {
pieChart.visibility = View.GONE
totalUsage.visibility = View.GONE
deleted = true
appsViewModel.removeAll()
Toast.makeText(this, "Successfully deleted all", Toast.LENGTH_SHORT).show()
}
}
private fun otherMethod(){
...
if (!deleted) {
totalUsage.text = "Total usage today: $hour"
totalUsage.visibility = View.VISIBLE
}
}
When I click on an icon it doesn't change color unless I click it again, it goes to the activity but only the first icon remains highlighted. I have to click it again for the icon to change color. What is wrong with my code?
class ProfileActivity : BaseActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
val navigationBar = findViewById<BottomNavigationView>(R.id.navigation_bar)
navigationBar.setOnNavigationItemSelectedListener(navigation_bar)
}
private val navigation_bar = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.nav_profile -> {
// startActivity(Intent(this#ProfileActivity, ProfileActivity::class.java))
return#OnNavigationItemSelectedListener true
}
R.id.nav_explore -> {
startActivity(Intent(this#ProfileActivity, ExploreActivity::class.java))
return#OnNavigationItemSelectedListener true
}
R.id.nav_store -> {
startActivity(Intent(this#ProfileActivity, StoreActivity::class.java))
return#OnNavigationItemSelectedListener true
}
R.id.nav_board -> {
startActivity(Intent(this#ProfileActivity, BoardActivity::class.java))
return#OnNavigationItemSelectedListener true
}
}
false
} }
You are using activity. use Fragment. i think it will be solved.
You are using different activities for each item.
You have to handle the selected items in your activities with something like:
navigationBar.setSelectedItemId(R.id.nav_explore)
I have an activity that shows two fragments at the same time on tablet and one at a time on handset. Because I am making the app for both tablets and android, I have to separate the functionality of the navigation between fragments into a separate function "displaySecondFragmentOnHandset()".
On the smaller handset when a move from the first fragment to the second and the then try to go back to the first, the screen is blank.
MyActivity:
class CentralActivity : AppCompatActivity() {
val manager = supportFragmentManager
var firstFrag : FirstFrag? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
var ft: FragmentTransaction = manager.beginTransaction();
firstFrag = FirstFrag.newInstance()
ft.add(R.id.real_container, firstFrag!!, "firstFrag")
if (screenLayoutSize >= Configuration.SCREENLAYOUT_SIZE_LARGE) {
var secondFrag = SecondFrag.newInstance()
ft.add(R.id.real_container, secondFrag, "secondFrag")
}
ft.commit()
}
fun displaySecondFragmentOnHandset (){
var secondFrag = SecondFrag.newInstance()
var ft: FragmentTransaction = manager.beginTransaction();
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
}
Then in my FirstFragemnt, if i'm on a smaller handset I do:
class FirstFragemnt : Fragment() {
private var viewModel: SharedViewModel? = null
fun goToSecondFragment(){
if (screenLayoutSize!! < Configuration.SCREENLAYOUT_SIZE_LARGE) {
viewModel!!.setMsgInCommunicator(collection)
var centralActivity: CentralActivity = activity as CentralActivity
centralActivity.displaySecondFragmentOnHandset()
}
}
The problem is when I press the back button to go to the first fragment, The screen is blank.
in CentralActivity replace
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
with
ft!!.add(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
The solution was to put the fun goToSecondFragment() inside of override fun onViewCreated