Let's take two generic methods:
fooA(), which onSuccess, it updates activity A
fooB(), which onSuccess invokes fooA()
that send an asynchronous Volley request each to my server.
I would like to avoid in Activity A this pattern:
class A : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_A)
fooB()
}
fun fooB(): {
volleyRequest() {
onSuccess(): fooA()
onFailure(): Log.e("ERROR")
}
}
fun fooA(): {
volleyRequest() {
onSuccess(): // do something on UI
onFailure(): Log.e("ERROR")
}
}
}
Instead, I would like to keep my code clean and define these requests in a separate RequestManager and have something like:
class A : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_A)
RequestManager.fooB().onSuccess().RequestManager.fooA().onSuccess() {
// do something on UI
}
}
}
Separating methods in this way would allow me to reuse fooA() and fooB() in other ways throughout my application.
Is it possible in Android (Kotlin) to deal with asynchronous tasks in this way?
Related
Sometimes, I want to handle the back button being pressed by the user myself. My (example) code looks something like this:
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test)
}
override fun onBackPressed() {
if (checkSomeCondition()) {
// do nothing
} else {
super.onBackPressed()
}
}
private fun checkSomeCondition() = false
}
I get notified when back is pressed, and then I decide if I want to handle it, or let the system handle it by calling super.onBackPressed().
Since onBackPressed() is now deprecated, I replace it by using the OnBackPressedDispatcher:
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (checkSomeCondition()) {
// do nothing
} else {
onBackPressedDispatcher.onBackPressed()
}
}
})
}
private fun checkSomeCondition() = false
}
The problem with this code: by calling onBackPressedDispatcher.onBackPressed(), I call my own callback, creating an infinite loop instead of handing this over to the system like I would with super.onBackPressed().
This can be circumvented when temporarily disabling my callback like:
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (checkSomeCondition()) {
// do nothing
} else {
this.isEnabled = false
onBackPressedDispatcher.onBackPressed()
this.isEnabled = true
}
}
})
but that seems rather awkward and doesn't seem like it was intended that way.
What's the correct usage of the OnBackPressedDispatcher here that lets me either handle the back button press myself or hand it over to the system?
PS: I have seen this question about the deprecation of onBackPressed, but it doesn't answer my much more specific question.
As per the Basics of System Back video, the whole point of the Predictive Back gesture is to know ahead of time what is going to handle back.
That means that you should never have just-in-time logic such as checkSomeCondition as part of your call to handleOnBackPressed.
Instead, as explained in the Custom back navigation documentation, you should be updating the isEnabled state of your OnBackPressedCallback ahead of time whenever the conditions you used to check in checkSomeCondition changed. This ensures that your callback is only invoked when your condition is already true and is disabled when the condition is false, thus allowing the default behavior to occur.
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test)
// Instead of always setting enabled to true at the beginning,
// you need to check the state ahead of time to know what the
// initial enabled state should be
val isConditionAlreadySet = checkSomeCondition()
val callback = object : OnBackPressedCallback(
isConditionAlreadySet
) {
override fun handleOnBackPressed() {
// Since you've handled isEnabled correctly, you know
// your condition is set correctly here, so you can
// unconditionally do what you need to do to handle back
}
}
// This is the key part - now you know ahead of time
// when the condition changes, which lets you control
// when you want to handle back or not
setOnSomeConditionChangedListener { isConditionMet ->
callback.isEnabled = isConditionMet
}
onBackPressedDispatcher.addCallback(this, callback)
}
private fun checkSomeCondition() = false
private fun setOnSomeConditionChangedListener(
(isConditionMet: Boolean) -> Unit
) {
// The implementation here will depend on what
// conditions checkSomeCondition() actually depends on
}
}
Hey I am working in kotlin flow in android. I noticed that my kotlin flow collectLatest is calling twice and sometimes even more. I tried this answer but it didn't work for me. I printed the log inside my collectLatest function it print the log. I am adding the code
MainActivity.kt
class MainActivity : AppCompatActivity(), CustomManager {
private val viewModel by viewModels<ActivityViewModel>()
private lateinit var binding: ActivityMainBinding
private var time = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupView()
}
private fun setupView() {
viewModel.fetchData()
lifecycleScope.launchWhenStarted {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.conversationMutableStateFlow.collectLatest { data ->
Log.e("time", "${time++}")
....
}
}
}
}
}
ActivityViewModel.kt
class ActivityViewModel(app: Application) : AndroidViewModel(app) {
var conversationMutableStateFlow = MutableStateFlow<List<ConversationDate>>(emptyList())
fun fetchData() {
viewModelScope.launch {
val response = ApiInterface.create().getResponse()
conversationMutableStateFlow.value = response.items
}
}
.....
}
I don't understand why this is calling two times. I am attaching logs
2022-01-17 22:02:15.369 8248-8248/com.example.fragmentexample E/time: 0
2022-01-17 22:02:15.629 8248-8248/com.example.fragmentexample E/time: 1
As you can see it call two times. But I load more data than it call more than twice. I don't understand why it is calling more than once. Can someone please guide me what I am doing wrong. If you need whole code, I am adding my project link.
You are using a MutableStateFlow which derives from StateFlow, StateFlow has initial value, you are specifying it as an emptyList:
var conversationMutableStateFlow = MutableStateFlow<List<String>>(emptyList())
So the first time you get data in collectLatest block, it is an empty list. The second time it is a list from the response.
When you call collectLatest the conversationMutableStateFlow has only initial value, which is an empty list, that's why you are receiving it first.
You can change your StateFlow to SharedFlow, it doesn't have an initial value, so you will get only one call in collectLatest block. In ActivityViewModel class:
var conversationMutableStateFlow = MutableSharedFlow<List<String>>()
fun fetchData() {
viewModelScope.launch {
val response = ApiInterface.create().getResponse()
conversationMutableStateFlow.emit(response.items)
}
}
Or if you want to stick to StateFlow you can filter your data:
viewModel.conversationMutableStateFlow.filter { data ->
data.isNotEmpty()
}.collectLatest { data ->
// ...
}
The reason is collectLatest like backpressure. If you pass multiple items at once, flow will collect latest only, but if there are some time between emits, flow will collect each like latest
EDITED:
You really need read about MVVM architecture.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupView()
}
private fun setupView() {
if (supportFragmentManager.findFragmentById(R.id.fragmentView) != null)
return
supportFragmentManager
.beginTransaction()
.add(R.id.fragmentView, ConversationFragment())
.commit()
}
}
Delele ActivityViewModel and add that logic to FragmentViewModel.
Also notice you don't need use AndroidViewModel, if you can use plain ViewModel. Use AndroidViewModel only when you need access to Application or its Context
What is the problem?
I want to add this custom window callback in each activity already implemented in the app, currently I need to manually modify the code of the onCreate method of each activity or make it inherit a class that already has onCreate in the desired way, but I need that this process could be performed without modifying the app's existing code, just adding some initialization.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.callback = CustomCallback(window.callback, this)
}
Inside you app Application Class (you'll need to create one, if not done yet), on "onCreate" method, call
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//Code here will run for every activity
}
}
You need a BaseActivity
class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//your code
}
All of your activities need to extends BaseActivity
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
}
I know the concept of "Interfaces" but I've hard time to understand how to use them in android development.
Let's say I created an interface to decide if to show progress bar or not -
interface ProgressBarInterface {
fun showProgressBar()
fun hideProgressBar()
}
And I implement this inside BaseActivity/MainActivity in single Activity app:
class BaseActivity : AppCompatActivity() , ProgressBarInterface {
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun showProgressBar() {
}
override fun hideProgressBar() {
}
}
And inside my other activity I've a button, that when I click on it, I want to trigger showProgressBar in the base activity:
button.setOnClickListener {
//Show progress bar
}
How can I interact with the interface to trigger the function inside base activity?
Since you already are implementing the interface in your BaseActivity, you can then just add what you need to do inside the interface methods, and then call them up in any point in your activity, if what you are looking for is to extend this BaseActiviy into more activities you will need to make this BaseActivity abstract then you can extend in each activity this BaseClass and just use the interface methods
abstract class BaseActivity : AppCompatActivity() , ProgressBarInterface {
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun showProgressBar() {
progressBar.visibility = View.VISIBLE
}
override fun hideProgressBar() {
progressBar.visibility = View.GONE
}
}
and then in your Activities you can extend from BaseActivity() and just use your interface methods as you have defined in that BaseActivity() to prevent coding them again, you can do
class FirstActivity : BaseActivity() {
...
button.setOnClickListener {
showProgressBar()
}
An easier way to show and hide the views? Use extension functions
fun View.show() {
this.visibility = View.VISIBLE
}
fun View.hide() {
this.visibility = View.GONE
}
you can define that extensions in any class, for example ViewUtils.kt and then just call
button.setOnClickListener {
progressBar.show()
}
or
button.setOnClickListener {
progressBar.hide()
}
I have 3 Parts to my Project: A Model that does calculations, some Fragments that display the UI and send Trigger to my third part, the main activity. I did all my Fragments with some interfaces like Communicating with Other Fragments.
However now I need one of the part of my Model to trigger some UI changes. And I don't know how to do that. Because my goal is to have one part of my Model send or trigger some functions so that the GUI gets updated but it doesn't know the GUI by itself. (it is totally independent from it)
In Main activity I override all the functions
class MainActivity : AppCompatActivity(), MimaFragment.elementSelectedListener, InstructionFragment.instructionSaveButtonClickedCallback , OptionFragment.optionSaveButtonClickedCallback, MimaFragment.UITrigger{
override fun abortOptions() {
extendNormal()
}
override fun updateMima() {
mimaFragment.updateView()
}
override fun normal() {
mimaFragment.drawArrows()
}}
Fragment exapmle:
class OptionFragment : Fragment() {
var optionCallback : optionSaveButtonClickedCallback? = null
interface optionSaveButtonClickedCallback{
fun updateMima()
fun abortOptions()
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
view?.findViewById(R.id.optionsAbort)?.setOnClickListener{
optionCallback?.abortOptions()
}
}
override fun onAttach(context: Context?) {
super.onAttach(context)
try {
optionCallback = context as optionSaveButtonClickedCallback
} catch (e : ClassCastException){
throw ClassCastException(activity.toString() + " must implementoptionSaveButtonClickedCallback")
}
}
}
That is how you usually do it and it works fine. Now to my Question is there a way to do it just like that for a non Fragment class? I tryed it like this:
class MimaModul(name: String, description : String, context: Context) : Element(name, description) {
val uiTrigger : UITrigger? = null
init{
try {
uiTrigger = context as UITrigger
} catch (e : ClassCastException){
Log.d("ClassCastException","Didn't implement uiTrigger")
}
}
fun step(){
//it does some stuff here and then calls for example
uiTrigger?.normal()
}
interface UITrigger{
fun normal()
}
}
However as I expected the UITrigger cast does not work. (it always catches an exception) Do you have any ideas how to solve this. Or how else to do it?
ideally I want MimaFragment to implement the interface. But that didn't work either.
class MimaFragment : Fragment(), MimaModul.UITrigger {
//other stuff
override fun normal() {
drawArrows()
}
}
So when ever my Model is done with a step it should trigger some UI change. And I tryed to avoid just doing a loop and update all Elements based on their status because that would take forever. (Though I see this as my only options at the moment)
Let me know if I was unclear and i shall elaborate.