How to write the same UI code from two activities in Kotlin? - android

I have 2 activities with similar UI layouts, which contain some TextViews in the same place, that receive some text. I want to avoid writing this code twice, so I would like to create a class that will do the writing for both activities. The problem is that I need to pass the ViewBinding pointer to this class and then based on the type write either to Activity1 or Activity2. How can I do this?
Here is a solution that works but I am having to write the same code twice.
Assume there are three TextViews.
// Activity1
class MainActivity : AppCompatActivity(), UiCommon {
private lateinit var uib: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uib = ActivityMainBinding.inflate(layoutInflater)
setContentView(uib.root)
// write common part
val draw = DrawUiCommon(uib)
draw.draw("a1_text1", "a1_text2", "a1_text3")
}
}
// Activity2
class MainActivity2 : AppCompatActivity(), UiCommon {
lateinit var uib: ActivityMain2Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uib = ActivityMain2Binding.inflate(layoutInflater)
setContentView(uib.root)
// write common part
val draw = DrawUiCommon(uib)
draw.draw("a2_text1", "a2_text2","a3_text3")
}
}
// Common part
class DrawUiCommon(val pt: androidx.viewbinding.ViewBinding){
fun draw(t1: String, t2: String, t3: String){
if (pt is ActivityMainBinding){
pt.textView1.text = t1
pt.textView2.text = t2
pt.textView3.text = t3
}
else if (pt is ActivityMain2Binding){
pt.textView1.text = t1
pt.textView2.text = t2
pt.textView3.text = t3
}
}
}

As #sashabeliy said, if the ui is exactly the same and the only difference is the data to show, then you can receive the extra data in the intent. Is possible to create a method to do the navigation:
companion object {
private const val ARG_TEXT_LIST = "ARG_TEXT_LIST"
fun navigate(context: Context, data: Array<String>) {
val intent = Intent(context, MainActivity::class.java).apply {
putExtra(ARG_TEXT_LIST, data)
}
context.startActivity(intent)
}
}
And then you can fetch the data in the onCreate lifecycle to populate the views:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(yourBinding.root)
val textList = intent.getStringArrayExtra(ARG_TEXT_LIST)
yourBinding.apply {
textView1.text = textList[0]
textView2.text = textList[1]
textView3.text = textList[2]
}
}

You didn't show your layout code, but based on your description, it sounds like you have some views in each activity layout that are the same.
Step 1 - extract those common views into it's own layout that you can resuse.
Step 2 - Reference the included layout:
// Activity2
class MainActivity2 : AppCompatActivity(), UiCommon {
lateinit var uib: ActivityMain2Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uib = ActivityMain2Binding.inflate(layoutInflater)
setContentView(uib.root)
// write common part
val draw = DrawUiCommon(uib.includedLayout) // <-- Use included layout that has the comment views
draw.draw("a2_text1", "a2_text2","a3_text3")
}
}
// Common part
class DrawUiCommon(val pt: IncludedLayoutViewBinding ){ // <---Now we know the type
fun draw(t1: String, t2: String, t3: String){
//if (pt is ActivityMainBinding){ // <-- No if check needed
pt.textView1.text = t1
pt.textView2.text = t2
pt.textView3.text = t3
//}
//else if (pt is ActivityMain2Binding){ // <-- No branch needed
// pt.textView1.text = t1
// pt.textView2.text = t2
// pt.textView3.text = t3
//}
}
}
OPTIONAL BONUS:
Create a Custom View that encapsulates this common layout and behavior.
Sample activity layout:
<LinearLayour>
<MyCustomView>
// OTHER THINGS
</LinearLayour>
Sample Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uib = ActivityMain2Binding.inflate(layoutInflater)
setContentView(uib.root)
// write common part
uib.myCustomView.setValues("a2_text1", "a2_text2","a3_text3")
}
Sample Custom View:
class MyCustomView(...) {
// Important init stuff up here ...
fun setValues(t1: String, t2: String, t3: String) {
// Custom View creates and has its own binding
binding.textView1.text = t1
binding.textView2.text = t2
binding.textView3.text = t3
}
}

Related

I am confused with how lifecycleScope in android studio works

I am confused about how flow.collect works. Because in the lifecycleScope below I already say that a should be assigned by the value of data in my database. However, the value of a is still the string of "Hi" instead of "Hello".
class MainActivity : AppCompatActivity() {
private var binding: ActivityMainBinding? = null
private var a: String = "Hi"
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding?.root)
val somethingDao = SomethingDatabase.getDatabase(this).somethingDao()
lifecycleScope.launch {
somethingDao.insert(SomethingModel("Hello"))
somethingDao.fetchAllSomething().collect {
a = it[it.size - 1].name
}
}
println(a)
}
}
this is all of the information in my database
lifecycleScope.launch will start a coroutine, to make it simple the code inside lifecycleScope.launch will be executed in another thread and it will take some time until inserting data and reading it from database, but println(a) is on the main thread so it will be executed before this line a = it[it.size - 1].name, so your println(a) should be inside lifecycleScope.launch like this:
class MainActivity : AppCompatActivity() {
private var binding: ActivityMainBinding? = null
private var a: String = "Hi"
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding?.root)
val somethingDao = SomethingDatabase.getDatabase(this).somethingDao()
lifecycleScope.launch {
somethingDao.insert(SomethingModel("Hello"))
somethingDao.fetchAllSomething().collect {
a = it[it.size - 1].name
println(a)
}
}
}
}
Note: take a look on kotlin coroutines to better understand

New to coding - Android Dice Roll App with 2 Results

This is my first post and I am brand new to coding so please let me know if I've missed anything for getting some help.
I'm taking the Google Android Dev tutorials. The tutorial is walking me through creating a dice roll app. I completed that and for an extra challenge practice at the end it recommends getting two results from one button click.
I tried doing that in this code:
package com.example.diceroller
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
rollButton.setOnClickListener { rollDice() }
rollButton.setOnClickListener { rollDice2() }
}
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val resultTextView: TextView = findViewById(R.id.textView)
resultTextView.text = diceRoll.toString()
}
private fun rollDice2() {
val dice2 = Dice2(6)
val diceRoll2 = dice2.roll2()
val resultTextView: TextView = findViewById(R.id.textView2)
resultTextView.text = diceRoll2.toString()
}
}
class Dice(private val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
class Dice2(private val numSides: Int) {
fun roll2(): Int {
return (1..numSides).random()
}
}
I don't get any errors, but when I run the app it only shows one result (the second result). Again, I'm new to all this and maybe I'll learn it later, but looking for some help on why it only spits out one result. Any help is greatly appreciated and thank you in advance.
It's not made clear by the documentation for setOnClickListener, but a View (which a Button is a type of) can only have one click listener. So when you do this:
rollButton.setOnClickListener { rollDice() }
rollButton.setOnClickListener { rollDice2() }
you're setting a listener that calls rollDice(), and then replacing it with another one that calls rollDice2(). You need to do everything in a single listener!
rollButton.setOnClickListener {
rollDice()
rollDice2()
}
so when you click the button, it'll run the code in that lambda function you're passing in as your listener, so it'll call rollDice() and then rollDice2().
As a general rule, if a function is named like setListener, with a set, then it's usually a single listener that you can set (or unset, usually with null). If it's named something like addListener, with an add, that implies you can add more to what's already there, i.e. you can have multiple listeners or whatever.
I'm not saying this will always be true (always check the documentation, or the source code if you can! See what it's actually doing) but it's a good rule of thumb in my experience - but you should always check if you're unsure!
You can also achieve the same result by rolling the same dice twice
package com.example.diceroller
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
// setOnClickListner -> defines what to execute on button click
rollButton.setOnClickListener { rollDice() }
}
private fun rollDice() {
// create two dice's each with '6' sides
var dice_1 = Dice(6)
var dice_2 = Dice(6)
// roll the two dice's
val dice_1_roll = dice_1.roll()
val dice_2_roll = dice_2.roll()
// bind the obtained result to the corresponding 'textView'
val resultTextView_1: TextView = findViewById(R.id.textView)
val resultTextView_2: TextView = findViewById(R.id.textView)
// fun roll() in Dice: Class return 'Int' so convert into 'String'
resultTextView_1.text = dice_1_roll.toString()
resultTextView_2.text = dice_2_roll.toString()
}
}
class Dice(private val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
package com.example.diceroller
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
/**
* This activity allows the user to roll a dice and view the result
* on the screen.
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
rollButton.setOnClickListener { rollDice() }
}
/**
* Roll the dice and update the screen with the result.
*/
private fun rollDice() {
// Create new Dice object with 6 sides and roll it
val myFirstDice = Dice(6)
val diceRollFirst = myFirstDice.roll()
// Update the screen with the dice roll
val resultTextView: TextView = findViewById(R.id.textView)
resultTextView.text = diceRollFirst.toString()
val mySecondDice = Dice(6)
val diceRollSecond = mySecondDice.roll()
val resultTextView2: TextView = findViewById(R.id.textView2)
resultTextView2.text = diceRollSecond.toString()
}
class Dice(private val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
}

How to pass data from an AppCompatDialog to an AppCompatActivity

I don't know how to pass data from the dialog fragment to the Activity. I have an Activity, which creates the Dialog. From this Dialog I want to pass Data to another Activity. Anyone know I can do this?
this is my 1st Activity:
class EinkaufslisteActivity : AppCompatActivity() {
//override val kodein by kodein()
//private val factory : EinkaufsViewModelFactory by instance()
#SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_einkaufsliste)
val database = EinkaufDatenbank(this)
val repository = EinkaufsRepository(database)
val factory = EinkaufsViewModelFactory(repository)
val viewModel = ViewModelProviders.of(this, factory).get(EinkaufsViewModel::class.java)
val adapter = ProduktAdapter(listOf(), viewModel)
rvVorratsliste.layoutManager = LinearLayoutManager(this)
rvVorratsliste.adapter = adapter
viewModel.getAllProdukte().observe(this, Observer {
adapter.items = it
adapter.notifyDataSetChanged()
})
adapter.setOnItemClickListener {
val produkt = it
Intent(this, VorratslisteActivity::class.java).also {
it.putExtra("EXTRA_PRODUKT", produkt)
}
EinkaufslisteProduktGekauftDialog(this, produkt, object : AddDialogListener{
override fun onAddButtonClicked(produkt: Produkt) {
}
override fun onAddButtonClickedVorrat(produktVorrat: ProduktVorrat) {
viewModel.delete(produkt)
}
}).show()
}
This is my Dialog:
ass EinkaufslisteProduktGekauftDialog (context: Context, var produkt : Produkt?, var addDialogListener: AddDialogListener?) : AppCompatDialog(context){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_einkaufsliste_produkt_gekauft)
tvProduktgekauftName.text = produkt?.name.toString()
etProduktGekauftAnzahl.hint = produkt?.anzahl.toString()
btnProduktGekauftOk.setOnClickListener {
val name = tvProduktgekauftName.text.toString()
val anzahl = etProduktGekauftPreis.text.toString()
val datum = etProduktGekauftDatum.text.toString()
val preis = etProduktGekauftPreis.text.toString()
if(name.isEmpty() || anzahl.isEmpty()){
Toast.makeText(context, "Bitte fülle alle Felder aus", Toast.LENGTH_SHORT).show()
return#setOnClickListener
}
val produktVorrat = ProduktVorrat(name, anzahl.toInt(), datum)
addDialogListener?.onAddButtonClickedVorrat(produktVorrat)
dismiss()
}
This is my 2nd Activity:
class VorratslisteActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vorratsliste)
val database = EinkaufDatenbank(this)
val repository = VorratsRepository(database)
val factory = VorratViewModelFactory(repository)
val viewModel = ViewModelProviders.of(this, factory).get(VorratViewModel::class.java)
val adapter = ProduktVorratAdapter(listOf(), viewModel)
rvVorratsliste.layoutManager = LinearLayoutManager(this)
rvVorratsliste.adapter = adapter
viewModel.getAllProdukteVorratsliste().observe(this, Observer {
adapter.items = it
adapter.notifyDataSetChanged()
})
val produkt = intent.getSerializableExtra("EXTRA_PRODUKT") as? ProduktVorrat
if(produkt != null) {
viewModel.upsertVorrat(produkt)
}
btnVorratNeuesProdukt.setOnClickListener {
VorratProduktHinzufuegenDialog(this,
object : AddDialogListener {
override fun onAddButtonClicked(produkt: Produkt) {
TODO("Not yet implemented")
}
override fun onAddButtonClickedVorrat(produktVorrat: ProduktVorrat) {
viewModel.upsertVorrat(produktVorrat)
}
}).show()
}
The "produkt" in activity 2 is null and i don't know why
Since you are already using a ViewModel in your code, add a LiveData variable in your view model and set that live data on the Dialog.
To get the value of the live data from another activity, ensure that you are using the same view model instance (using the activity view model factory). Then, you can access that view model (and live data) from that activity.
In this way, you have a single source of data that is shared between multiple ui components (activity, fragment, dialogs)
Check the official docs for Live Data here: https://developer.android.com/topic/libraries/architecture/livedata
ActivityA launches Dialog
Dialog passes result back to ActivityA
ActivityA launches ActivityB passing result from Dialog

Passing value through Interface

I am new to kotlin and i am trying to pass a value of checked radio button from one class to another activity through interface. I have an interface named RadioGroupHelperInterface as
interface RadioGroupHelperInterface {
fun onSelect(selectedItem: String)
}
Then i have a class from where i want to pass the value of checked radio button.
class GRadioGroupHelper {
private val radioGroupHelperInterface: RadioGroupHelperInterface? = null
fun setRadioExclusiveClick(parent: ViewGroup?) {
val radios: List<RadioButton>? = parent?.let { getRadioButtons(it) }
if (radios != null) {
for (radio in radios) {
radio.setOnClickListener { v ->
val r: RadioButton = v as RadioButton
r.isChecked = true
radioGroupHelperInterface?.onSelect(r.text as String)
checkedValue = r.text as String
for (r2 in radios) {
if (r2.getId() !== r.getId()) {
r2.isChecked = false
}
}
}
}
}
}
}
Finally my activity is as follows:
class ChooseCategoryActivity : AppCompatActivity(), View.OnClickListener,RadioGroupHelperInterface {
var radioGRadioGroupHelper=GRadioGroupHelper()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_choose_category)
setListener()
val parent: ViewGroup = findViewById(R.id.svCategories)
radioGRadioGroupHelper.setRadioExclusiveClick(parent)
}
override fun onSelect(selectedItem: String) {
Log.e("Here","reached")
Log.e("value",selectedItem)
Toast.makeText(this,selectedItem,Toast.LENGTH_LONG).show()
}
}
But i am not able to get the value that i have checked in the radio box from the activity though the value can be printed in the RadioGRadioGroupHelper class. Can anybody please help me?
You don't set radioGroupHelpedInterface field to any value except for null, which is its initial state. Why don't you try this:
Declare your GRadioGroupHelper as following:
class GRadioGroupHelper (private val helperInterface: RadioGroupHelperInterface) {
// All your logic remains the same
}
This will allow you to avoid nullability of the RadioGroupHelperInterface instance and you will also be able to set it via constructor like this in the activity:
val radioGRadioGroupHelper = GRadioGroupHelper(this)
Note that I changed var to val as we don't expect your radioGRadioGroupHelper to change.

Trying to show the product of two EditTexts as a TextView in another activity

I am having a ton of trouble passing the product of two EditTexts to a TextView in another activity. Here is my code for MainActivity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button1: Button = findViewById(R.id.button1)
val editText1: EditText = findViewById(R.id.editText1)
val editText2: EditText = findViewById(R.id.editText2)
val firstNumber = editText1.toString().toInt()
val secondNumber = editText2.toString().toInt()
val product = firstNumber * secondNumber
button1.setOnClickListener{
val intent = Intent(this, Activity2::class.java)
intent.putExtra("RESULT_PRODUCT", product)
startActivity(intent)
}
}
}
Here is my code for Activity2:
class Activity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_2)
val product = intent.getIntExtra("RESULT_SUM", 0)
textView1.text = product.toString()
}
}
I am relatively new to Kotlin and Android Studio but this has caused crashes left and right.
First of all, You have to calculate the product inside OnClickListener to get correct result.
button1.setOnClickListener{
val firstNumber = editText1.text.toString().trim()
val secondNumber = editText2.text.toString().trim()
if(!(firstNumber.isEmpty() or secondNumber.isEmpty())) {
val product = firstNumber.toInt() * secondNumber.toInt()
val intent = Intent(this, Activity2::class.java)
intent.putExtra("RESULT_PRODUCT", product)
startActivity(intent)
} else {
//Show messages
}
}
And then you have to use the exact key RESULT_PRODUCT that you use in your activity to pass data through intent
val product = intent.getIntExtra("RESULT_PRODUCT", 0)
You are passing "RESULT_PRODUCT" from MainActivity but getting "RESULT_SUM" in your Activity2. You should use intent.getIntExtra("RESULT_PRODUCT", 0) in you second activity.

Categories

Resources