I'm going to MenuFragment from MainActivty and calling RecyclerViewAdapter inside in MenuFragment class to set my data to show . then I want to set the callback in my Adapter using higher order function (Kotlin language) to send the data to MainActivity to change something in BottomNavigationView UI.
MainActivty class:
val callbackFromAdapter=RecyclerAdapterInside(null,null,this,::callbackAdapterInside)
private fun callbackAdapterInside(OrderList:MutableList<OrderLive>)
{
var totalSum=0
for(elements in OrderList)
{
totalSum+=elements.getcount()
}
Log.d("oncell", "$totalSum")
if(totalSum>0)
{
CoroutineScope(Dispatchers.Main).launch {
badge_current_order.visibility=View.VISIBLE
badge_current_order.setText(totalSum)
}
}
else
CoroutineScope(Dispatchers.Main).launch {
badge_current_order.setText(0)
badge_current_order.visibility=View.GONE
}
}
RecyclerAdapterInside class:
class RecyclerAdapterInside(
PlaceId:String?=null, var myReceivedData: List<ItemClass>?=null, val context:Context,
val CurrentCartUpdateCallback: ((OrderList: MutableList<OrderLive>) -> Unit?)? = null):RecyclerView.Adapter<RecyclerView.ViewHolder>() {
...
CoroutineScope(Dispatchers.IO).launch {
if(CurrentCartUpdateCallback!=null) { //never pass this condition
CurrentCartUpdateCallback!!(myOrderLive)
Log.d("tagtest", "Callback is called ")
}
}
}
I said I'm using RecyclerAdapterInside in my MenuFragment class to set data.
recyclerview.adapter=RecyclerAdapterInside(
id,
result,
this#MenuFragment.requireContext(),
null
)
for this reason I should create the type of null for CurrentCartUpdateCallback variable. Because I never use this callback in MenuFragmentClass and I always pass null when I don't need callback in this class. so I just use it in MainActivity class.
The problem is callbackAdapterInside is never called and CurrentCartUpdateCallback is always null in:
if(CurrentCartUpdateCallback!=null)
I don't use higher order function much. Do I have to use this callback in order of class numbers
Adapter->3->2->1 or can I use callback directly from the adapter class to the Main activity class ?
Related
I'm developing a huge section of my Android app in Jetpack Compose with the MVVM pattern.
I have a ViewModel father that is extended by all the other ViewModels. There, I have defined an open function which contains the initialization logic of each ViewModel that I need to call every time I enter in a new screen and to call again when something went wrong and the user clicks on the "try again" button.
abstract class MyFatherViewModel(): ViewModel() {
open fun myInitMethod() {}
fun onTryAgainClick() {
myInitMethod()
}
}
class MyScreen1ViewModel(): MyFatherViewModel() {
init {
myInitMethod()
}
override fun myInitMethod() {
super.myInitMethod()
// Do something
}
}
class MyScreen2ViewModel(): MyFatherViewModel() {
init {
myInitMethod()
}
override fun myInitMethod() {
super.myInitMethod()
// Do something
}
}
Is there a way I can call this method in the init function of MyFatherViewModel instead of doing it in all the children ViewModels? If I try to do that, it gives me the "Calling non-final function in constructor" warning and, of course, it doesn't work.
abstract class MyFatherViewModel(): ViewModel() {
open fun myInitMethod() {}
init {
myInitMethod()
}
fun onTryAgainClick() {
myInitMethod()
}
}
Is it possible to call a non-final function in constructor?
Technically yes, but you shouldn't. Kotlin is trying to protect you from problems here. If you call an open function from a constructor, it means you are running code from the child class before the parent class is completely initialized, and before the child class even started initializing. If the child implementation of the open function tries to access properties from the child class, unexpected things may happen. For instance, non-nullable properties could yield null (because not initialized yet), or primitive values could yield their type's default instead of the default value from their initializer:
fun main() {
Child()
}
open class Parent {
init {
initialize()
}
val id = 42
open fun initialize() = println("parent init")
}
class Child : Parent() {
val name = "Bob"
override fun initialize() = println("initializing $name, parent id=$id")
}
This prints the following output:
initializing null, parent id=0
I guess you can see why this is dangerous.
Maybe you should reconsider what you're trying to do with this try-again feature. Maybe a new view model should be instantiated instead (if try-again is to handle crashes, the state of the current view model may actually be bad enough to want to re-create it from scratch anyway).
how can I avoid the collect{} code execute again when navigate back to the fragment.
ViewModel class
private val _commitResult = MutableStateFlow<Map<String, Any>>(mapOf())
val commitResult: StateFlow<Map<String, Any>> = _commitResult
Fragment code like this:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.commitResult.collect { data ->
Logger.i("commitResult $data")
//navigate to another fragment
}
}
}
when I change the _commitResult value in viewModel first, jump to another fragment works fine.
unfortunately, when I go back to the fragment. collect{ // navigate to another fragment} will
excute again.
I know when back to the fragment. onCreateView excute again and viewModel will emit the data store
before, so thecollect { // navigate to another fragment} excute. How can I avoid this?
same as LiveData, I use Event to fix this with LiveData.
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
how can I handle this with stateflow? actually I don't like Event<.> to handle this,
am I use the stateflow in a wrong way? how I can fix this?
If anyone who can help, thanks in advance.
StateFlow keeps it's state, so I'd suggest either:
A) Use SharedFlow. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/
B) Use a pattern where you handle the dismissal of events
class Vm: ViewModel() {
private val mEvent = MutableStateFlow<MyResult?>(null)
val event = mEvent.asStateFlow()
fun dismissEvent() {
mEvent.value = null
}
}
class Frag: Fragment() {
override fun onViewCreated() {
vm.event.collect {
navigate()
vm.dismissEvent()
}
}
}
I am using MVVM in my app. When you enter a query and click search button, the chain is as follows: Fragment -> ViewModel -> Repository -> API -> Client. The client is where HTTP requests are made. But there is one thing here, the client needs to make a call and get a key from the server at initialization. Therefore, to prevent any call before it this first call completes, I need to be able to observe it from Fragment so that I can disable search button. Since each component in the chain can communicate with adjacent components, all components should have a state.
I am thinking to implement a StatefulComponent class and make all components to extend it:
open class StatefulComponent protected constructor() {
enum class State {
CREATED, LOADING, LOADED, FAILED
}
private val currentState = MutableLiveData(State.CREATED)
fun setState(newState: State) {
currentState.value = newState
}
val state: LiveData<State> = currentState
val isLoaded: Boolean = currentState.value == State.LOADED
val isFailed: Boolean = currentState.value == State.FAILED
val isCompleted: Boolean = isLoaded || isFailed
}
The idea is that each component observers the next one and updates itself accordingly. However, this is not possible for ViewModel since it is already extending ViewModel super class.
How can I implement a solution for this problem?
The most common approach is to use sealed class as your state, so you have any paramaters as you want on each state case.
sealed class MyState {
object Loading : MyState()
data class Loaded(data: Data) : MyState()
data class Failed(message: String) : MyState()
}
On your viewmodel you will have only 1 livedata
class MyViewModel : ViewModel() {
private val _state = MutableLiveData<MyState>()
val state: LiveData<MyState> = _state
fun load() {
_state.postCall(Loading)
repo.loadSomeData(onData = { data ->
_state.postCall(Loaded(data))
}, onError = { error -> _state.postCall(Failed(error.message)) })
}
// coroutines approach
suspend fun loadSuspend() {
_state.postCall(Loading)
try {
_state.postCall(Loaded(repo.loadSomeDataSupend()))
} catch(e: Exception) {
_state.postCall(Failed(e.message))
}
}
}
And on the fragment, just observe the state
class MyFragment : Fragment() {
...
onViewCreated() {
viewModel.state.observer(Observer {
when (state) {
// auto casts to each state
Loading -> { button.isEnabled = false }
is Loaded -> { ... }
is Failed -> { ... }
}
}
)
}
}
As João Gouveia mentioned, we can make stateful components quite easily using kotlin's sealed classes.
But to make it further more useful, we can introduce Generics! So, our state class becomes StatefulData<T> which you can use pretty much anywhere (LiveData, Flows, or even in Callbacks).
sealed class StatefulData<out T : Any> {
data class Success<T : Any>(val result : T) : StatefulData<T>()
data class Error(val msg : String) : StatefulData<Nothing>()
object Loading : StatefulData<Nothing>()
}
I've wrote an article fully explaining this particular implementation here
https://naingaungluu.medium.com/stateful-data-on-android-with-sealed-classes-and-kotlin-flow-33e2537ccf55
If you are using the composable ... You can use produce state
#Composable
fun PokemonDetailScreen(
viewModel: PokemonDetailVm = hiltViewModel()
) {
/**
* This takes a initial state and with that we get a coroutine scope where we can call a API and assign the data into the value
*/
val pokemonInfo = produceState<Resource<Pokemon>>(initialValue = Resource.Loading()) {
value = viewModel.getPokemonInfo(pokemonName)
}.value
}
How do I call a method in outer class from the inner class?
I can pass it as context, but I can't call methods on it
loginButton.setOnClickListener {
ssoManager.login(emailEditText.text.toString(), passwordEditText.text.toString())
.subscribe(object: Consumer<SSOToken> {
val intent = Intent(this#LoginActiviy, PasscodeActivity::class.java)
this#LoginActiviy.startActivity(intent)
})
I'm not sure what APIs you're using here, I'm gonna assume that your Consumer is java.util.function.Consumer for the sake of the answer.
You are writing code directly in the body of your object, and not inside a function. The first line of creating the Intent only works because you're declaring a property (and not a local variable!).
What you should do instead is implement the appropriate methods of Consumer, and write the code you want to execute inside there:
loginButton.setOnClickListener {
ssoManager.login()
.subscribe(
object : Consumer<SSOToken> {
val foo = "bar" // this is a property of the object
override fun accept(t: SSOToken) {
val intent = Intent(this#LoginActiviy, PasscodeActivity::class.java)
this#LoginActiviy.startActivity(intent)
}
}
)
}
I am trying to write a Handler in an Activity that needs a reference to the activity. If I write like this
class MainActivity : AppCompatActivity() {
private val mHandler = MainActivityHandler(this)
class MainActivityHandler(val activity: MainActivity) : Handler() {
override fun handleMessage(msg: Message?) {
when(msg?.what) {
MSG_CHANGE_TEXT -> {
activity.tv_logged.setText(R.string.title_main)
activity.mHandler.sendMessageDelayed(obtainMessage(SOMETHING), 3000)
}
// ...
}
}
}
}
This code compiles and works as expected. But if I try to pass a weak reference to the activity like this
class MainActivity : AppCompatActivity() {
private val mHandler = MainActivityHandler(WeakReference(this))
class MainActivityHandler(val activityRef: WeakReference<MainActivity>) : Handler() {
private val activity
get() = activityRef.get()
override fun handleMessage(msg: Message?) {
when(msg?.what) {
MSG_CHANGE_TEXT -> {
activity?.tv_logged.setText(R.string.title_main)
activity?.mHandler.sendMessageDelayed(obtainMessage(SOMETHING), 3000)
}
// ...
}
}
}
}
Now the compiler complains that tv_logged and mHandler are nullable receiver type and need to be accessed using ?.
I can understand that the val activity: MainAcitivity? inside the handler is nullable because it comes from WeakReference.get() but how come the properties in MainActivity are also nullable?
Its because the return type of activity?.tv_logged is (assuming its a TextView ) , TextView? . In Kotlin docs , where an alternative is proposed to checking null via if condition
Your second option is the safe call operator, written ?.
b?.length
This returns b.length if b is not null, and null otherwise. The type of this expression is Int? .
To perform a certain operation only for non-null values, you can use the safe call operator together with let:
activity?.let{ //access activity via `it`}
I understood this when I read the whole text at https://kotlinlang.org/docs/reference/null-safety.html#safe-calls
This has nothing to do with WeakReference as I suspected. It happens because safe call operator returns a nullable type even when accessing non-nullable type properties. (The doc doesn't really specify this as explicitly and clearly.)