viewModel in Kotlin Android - android

I'm trying to understand view models using kotlin for android, and I'm running into some difficulty. I have a very simple dummy app which allows a user to increment a number and then send that number to a second screen. That second screen will then display a random number between 0 and the sent number.
Here is the problem.
I understand how to send the data from the first page to the second using intents, and I know how to make a viewmodel in the second page. However, if I send the intent and then set the viewmodel equal to the set intent, it doesnt function properly. Rotating the screen will cause the intent to be resent and the viewmodel doesnt maintain the state of the data (the number rerandomizes).
Ideally, I'd like to just be able to just update the viewModel class in place of sending the intent, but the instance of the class is created when the second page is created, so that doesn't work.
Any ideas?
Based on the google codelabs "build my first android app" tutorial.
Here's my code; first page:
package com.example.patientplatypus.babbysfirstandroidapp
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.textView
import org.jetbrains.anko.db.PRIMARY_KEY
import org.jetbrains.anko.db.UNIQUE
import org.jetbrains.anko.db.createTable
import android.database.sqlite.SQLiteDatabase
import android.support.v4.content.ContextCompat.startActivity
import com.example.patientplatypus.babbysfirstandroidapp.R.id.textView
import org.jetbrains.anko.db.*
import org.jetbrains.anko.indeterminateProgressDialog
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main);
}
fun toastMe(view: View) {
val myToast = Toast.makeText(this, "Hello Toast!", Toast.LENGTH_SHORT)
myToast.show()
}
fun countMe (view: View) {
Log.d("insideCountMeCheck", "hey you are inside count me!")
val countString = textView.text.toString()
var count: Int = Integer.parseInt(countString)
count++
textView.text = count.toString()
}
fun randomMe (view: View) {
val randomIntent = Intent(this, SecondActivity::class.java)
val countString = textView.text.toString()
val count = Integer.parseInt(countString)
randomIntent.putExtra(SecondActivity.TOTAL_COUNT, count.toString())
startActivity(randomIntent)
}
}
Here's my code, second page:
package com.example.patientplatypus.babbysfirstandroidapp
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProviders
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.Toast
import java.util.*
import kotlinx.android.synthetic.main.activity_second.randomText
class CountViewModel : ViewModel() {
var TOTAL_COUNT = "total_count"
}
class SecondActivity : AppCompatActivity() {
lateinit var countModel: CountViewModel
companion object {
const val TOTAL_COUNT = "total_count"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.activity_second)
countModel = ViewModelProviders.of(this).get(CountViewModel::class.java)
countModel.TOTAL_COUNT = intent.getStringExtra(TOTAL_COUNT)
displayForRandomNum(countModel.TOTAL_COUNT);
showRandomNumber()
}
fun showRandomNumber() {
val count = countModel.TOTAL_COUNT.toInt()
val random = Random()
var randomInt = 0
if (count > 0) {
randomInt = random.nextInt(count + 1)
}
Log.d("randomFinal", Integer.toString(randomInt))
displayForRandomNum(Integer.toString(randomInt))
}
fun displayForRandomNum(totalCount: String){
randomText.text = totalCount
}
}

An orientation change causes the activity be destroyed then recreated. This means onCreate will be called on every rotate. The same intent that started the activity originally would still be available to it. So intent.getStringExtra(TOTAL_COUNT) would return the original value from the intent that start the activity every time the screen is rotated. ViewModel will retain the data through the rotation though.
Your issue is that your overriding you're ViewModel'sTOTAL_COUNT with the original value from the intent every time. What you can do is check that the TOTAL_COUNT value isn't "total_count" (meaning its already been set from the intent) before setting it in onCreate.

Related

Android Studio: Import not being recognized

I'm having an issue where I've imported an import in order for a piece of code to work but no matter how many times I import the import, the code isn't recognizing that it's there. I've already tried invalidating and restarting, multiple times. I've read that another solution to this is to Sync with File System, but I don't appear to have that option under File.
The import in question is import java.text.MessageFormat.format
import android.content.ContentValues.TAG
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import com.squareup.okhttp.internal.http.HttpDate.format
import java.math.BigDecimal
import java.sql.Time
import java.sql.Timestamp
import java.text.DateFormat
import java.text.MessageFormat.format
import java.util.*
import kotlin.collections.ArrayList
class RemindersActivity : AppCompatActivity() {
lateinit var petID: String
val db = Firebase.firestore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reminders)
displayReminders()
petID = intent.getStringExtra("petID").toString()
val fab: View = findViewById(R.id.fab_reminders)
fab.setOnClickListener {
val intent = Intent(this, AddReminderActivity::class.java)
intent.putExtra("petID", petID)
startActivity(intent)
}
}
override fun onStart() {
super.onStart()
displayReminders()
}
private fun displayReminders() {
val recyclerview = findViewById<RecyclerView>(R.id.recyclerview_reminders)
recyclerview.layoutManager = LinearLayoutManager(this)
db.collection("pets").document(petID).collection("reminders").get().addOnSuccessListener { result ->
val data = mutableListOf<RemindersData>()
for (document in result) {
val title = document.data["title"].toString()
val timestamp = document.data["timestamp"] as Long
val cal = Calendar.getInstance()
cal.timeInMillis = timestamp * 1000L
val date = DateFormat.format("dd-MM-yyyy hh:mm:ss aa", cal).toString() //<- code note recognizing import is format
val frequency = document.data["frequency"].toString()
data.add(RemindersData(title, date, frequency))
}
val adapter = RemindersAdapter(data)
recyclerview.adapter = adapter
}.addOnFailureListener { e->
Log.w(TAG, "Error getting documents", e)
}
}
}
It just seems that you have added a few imports from the java packages instead of the android packages. It can happen when you use auto import in the IDE and there are multiple options and you click the wrong one.
When that happens you either have to undo the import or fix it manually.
Remove the import for
import java.text.DateFormat
Add the import for
import android.text.format.DateFormat
Afterwards, if there are any unused imports left, you can remove those as well.

When I typed this it gave me "None of the following functions can be called with the arguments supplied."

package com.example.myandroidapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val calButtonVar = findViewById(R.id.calButton) as Button
var radiusTextvar = findViewById(R.id.radiusText) as EditText
var areaTextvar = findViewById(R.id.areaText) as TextView
calButtonVar.setOnClickListener{
var calArea: Double = 3.14 * radiusTextvar.text
areaTextvar.text = calArea.toString()
}
}
}
at calArea is where the error appears, i tried swapping var with val but it didnt work so I dont know what should I do and I also looked for similar problems but they didnt have it like me
Yes. As #CommonsWare wrote you can't just multiply editable text. use this instead:
var calArea: Double = 3.14 * radiusTextvar.text.toDouble()

how to save my image while changing the orientation of my phone in androidStudio using Kotlin

how to save the image while changing the orientation of my android app And this is basically a simple roll dice app i.e. base on the random number and on the basis of this we can easily choose image using when() in kotlin
package com.example.rollingdice
import android.location.Address
import android.media.Image
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.PersistableBundle
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import javax.crypto.KeyGenerator
import kotlin.properties.Delegates
import kotlin.random.Random
abstract class MainActivity : AppCompatActivity() {
lateinit var showingResult:ImageView
lateinit var diceButton:Button
lateinit var result
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
diceButton=findViewById(R.id.rollDice)
showingResult=findViewById(R.id.iv)
diceButton.setOnClickListener {
rollingDice()
}
}
// accordin to me this function is use to save the data or image while changing the
orientation
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
outState.putInt("img",result)
}
//private function named as rollingDice()
private fun rollingDice() {
var number : Int= Random.nextInt(6)+1
// taking random number input in when()
when (number){
// taking number in when() statement to choose the image
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
// else condition give the dice_6 name image as a result
}
showingResult.setImageResource(result)
//i'm taking the result as aimage in the image view
}
}

How do I save my inputted edit text and have it accessible when starting a new intent?

I'm trying to make a quiz app so that when the user clicks the plus button it takes them to a page where they input the question and answer which they can then save and is taken back to the previous page where a new button with the text set to the question is created. I have all of this coded and working except for the part where I can save the previously inputted question/answer activity state if the user wants to edit it. I've been told I need to send my question and answer from my main activity to my second activity in the form of a bundle but I have no idea how to access another layouts id in my main activity (and also how to use bundles in general). Can someone please help? Here's my code:
Main Activity:
package com.example.quest
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.material.floatingactionbutton.FloatingActionButton
class MainActivity : AppCompatActivity() {
private val questionActivityCode = 2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<FloatingActionButton>(R.id.btn2).setOnClickListener{
startActivityForResult(Intent(this#MainActivity, SecondActivity::class.java), questionActivityCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == questionActivityCode && resultCode == Activity.RESULT_OK) {
createNewButtonWithText(data?.getStringExtra("test") ?: "")
}
}
private fun createNewButtonWithText(text: String)
{
val newbutton = Button(this#MainActivity)
val layout = findViewById<LinearLayout>(R.id.mainlayout)
newbutton.text = text
newbutton.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
newbutton.width=1010
newbutton.height=300
newbutton.gravity = Gravity.CENTER
newbutton.translationX= 65F
newbutton.setTextColor(Color.parseColor("#FFFFFFFF"))
newbutton.setBackgroundColor(Color.parseColor("#250A43"))
layout.addView(newbutton)
val inflator = layoutInflater
val builder = AlertDialog.Builder(this)
val intent = Intent(this#MainActivity, SecondActivity::class.java)
newbutton.setOnClickListener{
val dialogLayout = inflator.inflate(R.layout.text, null)
with(builder) {
setTitle(newbutton.text)
setPositiveButton("Edit"){dialog, which ->
startActivity(intent)
}
setNegativeButton("Cancel"){dialog, which ->
Log.d("Main", "Negative button clicked")
}
setView(dialogLayout)
show()
}
}
}}
Second Activity:
package com.example.quest
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.EditText
import com.google.android.material.floatingactionbutton.FloatingActionButton
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val question = findViewById<EditText>(R.id.question)
findViewById<FloatingActionButton>(R.id.btn3).setOnClickListener{
val questiontext = question.text.toString()
val returnIntent = Intent()
returnIntent.putExtra("test", questiontext)
setResult(Activity.RESULT_OK, returnIntent)
finish()
}
}
}
Im not sure what do you want to achive. but I am trying to seperate your question into 2 questions.
You want to save your inputed text, so that you can edit it latter.
You can use Shared Preference Manager
Sample :
/*Save your values to SharedPreference*/
// create shared Prefrence Manager
// edit() This allows you to write key-value pairs to
//SharedPreferences.
val sharedPre = PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
// add key-value pairs to SharedPreferences
sharedPre.putString("Key","Value")
// Instruct the SharedPreferences Editor instance to apply the changes.
sharedPre.apply()
/*Load your values from SharedPreference*/
val loadSharedPre = PreferenceManager.getDefaultSharedPreferences(applicationContext)
// its return Map with string and value
loadSharedPre.all
send my question and answer from my main activity to my second activity in the form of a bundle
You can use parcelable, for more details you can visi this site

How to use onActivityResult in Kotlin? (UPDATED)

I have two activities A and B. A is the homepage and when the plus button is clicked it goes to B where there are two edit texts (question and answer). When both are inputted it and the check button is clicked, it is taken back to A where a new button appears with the text set to the inputted question. I have an inflated view which allows the user to edit the question and answer when clicking the button. The problem is that when I try to use intent to go back to activity B, the previously inputted texts are gone. I tried to use onActivityResult but its not working. Does anyone have any insight?
UPDATE:
I'm going to put my updated code below. I figured out how to send inputs between each activity and now each dynamically created button saved the individual pairs of edit texts. The problem I'm running across now is that it only saves the edit texts once and doesn't allow me to change it again. I've tried bundles, intent and shared preferences but somehow none of them have been working. I thought that having my intents at the top would allow me to save my data every time I inputted text but it isn't working. Here is the code I've been using:
Activity A:
package com.example.yesh
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.floatingactionbutton.FloatingActionButton
class MainActivity : AppCompatActivity() {
private val questionActivityCode = 2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<FloatingActionButton>(R.id.btn2).setOnClickListener{
startActivityForResult(Intent(this#MainActivity, SecondActivity::class.java), questionActivityCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == questionActivityCode && resultCode == Activity.RESULT_OK) {
createNewButtonWithText(data?.getStringExtra("test") ?: "", data?.getStringExtra("test2") ?: "")
}
}
private fun createNewButtonWithText(text: String, s: String)
{
val newbutton = Button(this#MainActivity)
val layout = findViewById<LinearLayout>(R.id.mainlayout)
newbutton.text = text
newbutton.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
newbutton.width=1010
newbutton.height=300
newbutton.gravity = Gravity.CENTER
newbutton.translationX= 65F
newbutton.setTextColor(Color.parseColor("#FFFFFFFF"))
newbutton.setBackgroundColor(Color.parseColor("#250A43"))
layout.addView(newbutton)
val inflator = layoutInflater
val builder = AlertDialog.Builder(this)
val intent1 = Intent(this#MainActivity, SecondActivity::class.java)
intent1.putExtra("test", text)
intent1.putExtra("test2", s)
newbutton.setOnClickListener{
val dialogLayout = inflator.inflate(R.layout.text, null)
with(builder) {
setTitle(newbutton.text)
setPositiveButton("Edit"){dialog, which ->
startActivity(intent1)
}
setNegativeButton("Delete"){dialog, which ->
layout.removeView(newbutton)
}
setView(dialogLayout)
show()
}
}
}
}
Activity B:
package com.example.yesh
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.floatingactionbutton.FloatingActionButton
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val question = findViewById<EditText>(R.id.question)
val answer = findViewById<EditText>(R.id.answer)
val value = intent.getStringExtra("test")
question.setText(value)
val value2 = intent.getStringExtra("test2")
answer.setText(value2)
findViewById<FloatingActionButton>(R.id.btn3).setOnClickListener {
val questiontext = question.text.toString()
val answertext = answer.text.toString()
saveData()
val returnIntent = Intent()
returnIntent.putExtra("test", questiontext)
returnIntent.putExtra("test2", answertext)
setResult(Activity.RESULT_OK, returnIntent)
finish()
}
}
private fun saveData() {
val question = findViewById<EditText>(R.id.question)
val answer = findViewById<EditText>(R.id.answer)
val txt = intent.getStringExtra("test2")
answer.setText(txt)
Toast.makeText(this, "Data saved", Toast.LENGTH_SHORT).show()
}
}
remember finish() in the B activity? It destroyed that activity already so there is not way to going back to it again, when you press the new button in activity A it and call
startActivityForResult(intent, 1) you are creating new instance of that activity , this new instance doesn't know about one which is already destroyed so you should also send the data in the intent which you want to display
I agree with Abhinav's answer, You cannot access an activity you destroyed with "finish()" as it removes the activity from memory.
The correct way of using onActivityResult() is for cases like fetching data from another activity.
For example, you have a form in Activity A and and there is a field called location. for that you can open Google maps in Activity B and when it is selected, you can return the location in activity A and display in already existing view in A.

Categories

Resources