I am confused with how lifecycleScope in android studio works - android

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

Related

Realm Kotlin - Delete realm object

https://www.mongodb.com/docs/realm/sdk/kotlin/realm-database/delete/delete-all-objects-of-a-type/
I am learning new kotlin-realm in my project. But i dont know how to delete objects. It keep showing error Caused by: io.realm.internal.interop.RealmCoreNotInATransactionException: [5]: Must be in a write transaction
this is the code :
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val realm by lazy { (application as CustomApplication).realm }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
realm.writeBlocking {
copyToRealm(Buku(
name = "Perjuangan Menjual Baju"
))
copyToRealm(Buku(
name = "Perang Saudara"
))
}
val buku = realm.query<Buku>("name BEGINSWITH $0", "pera")
Log.i("AOEU", "buku = $buku")
CoroutineScope(Dispatchers.Main).launch {
val query = realm.query<Buku>().find()
realm.write {
delete(query)
}
}
}
}
After days of finding solution. I just realized that the only mistake in my code is
val query = realm.query().find()
Which is should be replaced with
val query = this.query().find()

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

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
}
}

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

Can't access views or change their values

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
getCurrentCountry()
}
private fun getCurrentCountry(){
val api = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build().create(CountryReq::class.java)
GlobalScope.launch(Dispatchers.IO) {
val response = api.getCountries().awaitResponse()
if(response.isSuccessful){
val data = response.body()!!
withContext(Dispatchers.Main){
binding.selectCountry.text = "Demo text"
}
}
}
}
}
I'm trying to get data from an API and display it in a textview. But I can't seem to access the views or change its values from the GlobalScope.launch{} block.

Kotlin coroutines not downloading data

I am using Kotlin corountines in my Android Project. I am trying to download some data and display in a textview.
Following is my code
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv.setOnClickListener {
downloadData()
}
}
private fun downloadData() {
runBlocking {
pb_activity_main.visibility = View.VISIBLE
var data = ""
async {
data = downloadDataBlocking()
}.await()
tv.text = data
pb_activity_main.visibility = View.GONE
}
}
private fun downloadDataBlocking(): String {
val client = OkHttpClient()
val request = Request.Builder().url("https://jsonplaceholder.typicode.com/posts").build()
val response = client.newCall(request).execute()
return response.body()?.string() ?: ""
}
}
But the data is not downloaded. I am not able to figure out why.
I have included the internet permission in Manifest and the url is also working.
Try this:
class MainActivity : AppCompatActivity(), CoroutineScope {
private val job = Job()
override val coroutineContext = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv.setOnClickListener {
downloadData()
}
}
private fun downloadData() {
launch {
pb_activity_main.visibility = View.VISIBLE
tv.text = withContext(Dispatchers.IO) { downloadDataBlocking() }
pb_activity_main.visibility = View.GONE
}
}
private fun downloadDataBlocking(): String {
val client = OkHttpClient()
val request = Request.Builder().url("https://jsonplaceholder.typicode.com/posts").build()
val response = client.newCall(request).execute()
return response.body()?.string() ?: ""
}
}
First: you should never use runBLocking out of unit-testing or other special domain.
This function should not be used from coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
Second:
Coroutines are always related to some local scope in your application, which is an entity with a limited life-time, like a UI element.
That's why Activity implements CoroutineScope. Honestly, a better place for it is ViewModel or Presenter, but I don't see any in the code...
Third, it is quite pointless to useasync and await right after it's definition. Just use withContext then.

Categories

Resources