I read some kotlin project samples code, I find many author like to Code A.
I think Code B is more simpler.
1: Is Code B good way?
2: Can I always use private set intead of private val in Android Studio?
Code A
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState.asStateFlow()
Code B
var uiState = MutableStateFlow(InterestsUiState(loading = true))
private set
A, B are not the same code.
In code A, define another variable as StateFlow is preventing to change value in StateFlow from out of class.
In code B, you can update value in StateFlow from out of class.
Because you can refer MutableStateFlow.
Mutating state variable itself and Mutating state in StateFlow is different.
Observer observing StateFlow is received event when value in StateFlow is change but change StateFlow itself.
In other word, you should use code A for prevent to unexpected mutating from outer
I have seen this example
var userName by mutableStateOf("")
private set
but what i thought is best for a better encapsulation is this ,to deny the possibility of mutating the useName from outside the single source of truth (the viewmodel holder and responsible for managing the state )
private var _userName by mutableStateOf("")
val userName
get() = _userName
Related
I have the following ViewModel
#HiltViewModel
class ShareViewModel #Inject constructor(
private val taskRepository: TaskRepository
): ViewModel() {
private val searchAppBarStateMutableState: MutableState<SearchAppBarState> = mutableStateOf(SearchAppBarState.CLOSED)
val searchAppBarState: State<SearchAppBarState> = searchAppBarStateMutableState
private val listOfTaskMutableStateFlow = MutableStateFlow<List<TodoTaskEntity>>(emptyList())
val listOfTaskStateFlow = listOfTaskMutableStateFlow.asStateFlow()
}
I never expose mutableStateFlow as in the example above. And SonarLint will show a warning when doing this.
MutableStateFlow" and "MutableSharedFlow" should not be exposed
So I apply the same technique to the mutableState
However, If I do like this below, I don't get any warning.
val searchAppBarStateMutableState: MutableState<SearchAppBarState> = mutableStateOf(SearchAppBarState.CLOSED)
Just wondering what is the best practice for using MutableState with jetpack compose.
To use mutableState with viewmodel, define mutableState with private setter inside viewmodel, ex -
var isVisible by mutableState(false)
private set
By doing above we can read the mutable state from outside of viewmodel but not update it. To update create a public function inside viewmodel, ex -
fun setVisibility(value: Boolean) {
isVisible = value
}
By making a setter function we are following separation of concerns and having a single source of truth for editing the mutableState.
I think the error is in that you are setting
val searchAppBarState: State<SearchAppBarState> = searchAppBarStateMutableState
if you want to share private value as not mutable you shouldn't set it equal rather you can use get modifier
val searchAppBarState: State<SearchAppBarState> get() = searchAppBarStateMutableState
Also it would be better to name it with underscore as many developers are used to like:
private val _searchAppBarState: MutableState<SearchAppBarState> = mutableStateOf(SearchAppBarState.CLOSED)
val searchAppBarState: State<SearchAppBarState> get() = _searchAppBarState
The Code A is a sample code from the video.
I can't understand why the author need to use asStateFlow().
I think the Code B is OK, right?
Code A
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow.asStateFlow()
...
}
Code B
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow
...
}
Code A makes the stateFlow read-only while Code B exposes the mutable state flow as is.
The documentation of asStateFlow() is pretty clear about this:
Represents this mutable state flow as a read-only state flow.
If you take a look at the implemenation, you can see that it wraps the current (mutable) flow in a ReadonlyStateFlow which is, well, read-only:
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
ReadonlyStateFlow(this, null)
To make it easier to understand, if you use MainViewModel in a component, this outsider will be able to read the values but not write them in case of Code A. Meanwhile, if you use Code B, the outsider component may emit its own values to the state flow. This is usually undesirable as it should be the ViewModel's responsibility to emit data (this is called unidirectional data flow) as a response to actions coming from the observers (the view components).
The Code A is from offical sample code here.
I think I can pass _uiState to uiState directly, so I write Code B, it seems that Code B can work well.
Can I pass a MutableStateFlow object to a StateFlow variable directly?
Code A
class InterestsViewModel(
private val interestsRepository: InterestsRepository
) : ViewModel() {
// UI state exposed to the UI
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState.asStateFlow()
...
}
Code B
class InterestsViewModel(
private val interestsRepository: InterestsRepository
) : ViewModel() {
// UI state exposed to the UI
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState
...
}
Yes, this is fine. There is one small functional advantage to asStateFlow(). It does not simply upcast to a read-only StateFlow, but also wraps it in a read-only StateFlow, such that a receiver cannot cast it to a MutableStateFlow and use its publishing functions. However, this should be considered something that no reasonable code should ever do, because it is asking for trouble to forcibly mutate something that is declared as read-only.
There is no equivalent asList() for MutableList(), so I'm not sure why they felt Flows need this feature and Collections don't. I can't find any discussion of it. It appeared in the same commit that first introduced StateFlow and SharedFlow.
A secondary feature of asStateFlow() is that it permits writing this kind of code more concisely.
val uiState = _uiState.asStateFlow()
is easier and faster to write than
val uiState: StateFlow<InterestsUiState> = _uiState
or
val uiState = _uiState as StateFlow<InterestsUiState>
especially if the type is long or complicated, such as StateFlow<List<SomeApi.Subtype>>, for example.
I am reading through the existing codebase for my team, and I notice mutableListOf are always declared as val. In some scenarios, elements are added to the mutableListOf only once. E.g
val jobList = mutableListOf<JobActivity>()
jobList.addAll(job.activities)
In other scenarios, elements are added to mutableListOf in a loop. E.g
val jobList = mutableListOf<JobActivity>()
newJobList.filterScanType(retrieveJobType(JobContext.NEW)).forEach {
jobList.add(it)
}
Since the list is not initialized on creation, why not declare mutableListOf as var? A lot of examples found online also follow the same pattern of declaring mutableListOf as val.
Which is best to use in the 2 scenarios described, val or var?
I think it's declared as val because the list will be the same always, the only thing that changes is it's elements. You'll never do something like:
joblist = anotherList
And as #Taseer said, the properties of the object can be changed even if it's a val. For example:
data class Example(var name: String)
val exampleObject = Example("SomeName")
You'll still be able to do this:
exampleObject.name = "AnotherName"
But you can't do this:
exampleObject = anotherObject
The general rule of thumb while using Kotlin.
Difference in val and var
You may already know the differences but for the sake of an answer, I will repeat it. var lets you modify the reference of an object while val does not permit to change the reference of an object.
An object can be declared safely using either var or val keyword but the reason why you want to use val on an object(in most cases) is that you don't want to refer that class member with a new reference of a new instance of an object. That way, you always keep a reference to the original object and you can modify object properties.
In the case of var, though nothing wrong with it, you can still use it 'without' any problems. You can still access the object properties and modify them and also you will be able to refer that class member to a reference of a new object.
Example:
val myObject = MyObject()
myObject.something = 1 //can still modify object property.
myOjbect = MyObject() //re-referencing the object, NOT POSSIBLE
var myNewObject = MyNewObject()
myNewObject.someThing = "Hello world!" //can still modify object properties
myNewObject = MyNewObject() //can still reference it.
Why to use val over var in case of 'immutable' objects?
It gives you the security of not 'accidentally' placing a new reference.
But is there any performance benefit using val?
Answer: Final keyword benefit
val is more idiomatic for the reasons given in other answers and comments.
You said the val is not instantiated, but in your example code, it is.
val jobList = mutableListOf<JobActivity>()
is a factory that instantiates an empty MutableList<JobActivity>
Using this pattern (val not var, instantiated upon declaration) ensures that your code will never find an uninitialized or null value for jobList; and the compiler can prove it.
In short - there are no rules, its up to you
if you use val you can modify mutableList, but not reassign
if you need reassign another list to same variable, use var. In most cases you dont need it, thats why your team uses it frequently
Whether a variable is var or val distinguishes between a variable of which the value (reference) can be changed (var = mutable) or not (val = immutable).
You should always strive to use val over var to avoid unwanted side-effects (changing it in another thread for example).
In case of the MutableList you should most likely use a val, because you don't want to mutate the reference to the list but rather its contents.
Here an overview of your options:
// Do you want to change its reference (r) / contents (c)?
var a = mutableListOf(1, 2, 3) // r = yes, c = yes
var b = listOf(1, 2, 3) // r = yes, c = no
val c = mutableListOf(1, 2, 3) // r = no, c = yes
val d = listOf(1, 2, 3) // r = no, c = no
You create a variable with var that is mutable (that can change). Mutable means variable can be changed in future.
val is used when variable will not be changed in future that means constant or final.
Here changed means new value or new things will be assigned to the variable but
val list = mutableListOf()
in this list variable you assigned mutable list. You just changed the value of the list. But you didn't assign new instance or new value to the variable you just added and remove value from the list. That's it. So here list is immutable itself.
It will be mutable if you do things like below...
var list = mutableListOf()
list = mutableListOf()
Two initialization on the same variable.
Just ver confused about casting and how to set up class variables. In java it was possible to do
private var mSectionsStatePageAdapter : SectionsStatePagerAdapter? = null
private val mViewPager : ViewPager? = null
now we're in kotlin
class MainActivity : AppCompatActivity() {
private var mSectionsStatePageAdapter : SectionsStatePagerAdapter? = null
private val mViewPager : ViewPager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mytoolbar:Toolbar = findViewById(R.id.top_toolbar)
setSupportActionBar(mytoolbar)
mSectionsStatePageAdapter = SectionsStatePagerAdapter(getSupportFragmentManager())
mViewPager = findViewById(R.id.viewpager1)
setupViewPager(mViewPager)
}
fun setupViewPager(viewPager :ViewPager):Unit {
var adapter : SectionsStatePagerAdapter = SectionsStatePagerAdapter(getSupportFragmentManager())
adapter.addFragment(Fragment1(),"Fragment1")
viewPager.setAdapter(adapter)
}
I'm getting val can't be reassigned...
Error:(65, 24) Smart cast to 'ViewPager!' is impossible, because 'mViewPager' is a mutable property that could have been changed by this time
For Android, it is common to use lateinit var because you create the object outside of constructor (in onCreate, etc). You can go with two route:
lateinit var variable:Type
var variable:Type?
I would recommend if your variable should be available when you are ready to use it. You do not need to do null check. lateinit mean late initialization. Kotlin use null to represent not yet initialized, so you cannot use nullable type and assign null on it.
If your variable is nullable, then you should go the second way.
Beside, if you are handling views, you should use Android extension to do it. You don't need to findViewById in Activity or Fragment yourself.
There are two things happening here.
First, as #gyosida points out, you have defined your mPager property as val instead of var so it cannot be reassigned in the line mViewPager = findViewById(R.id.viewpager1). As #Joshua points out, you either have to make it a var or you need to make it a lateinit val to solve the problem of it not being initialised with the class instance.
The second is represented by the actual error you describe of, 'mutable property that could have been changed', and you will continue to see this if you make it a var. The approach of using lateinit is most likely the better idea.
However, to explain this error, in your method declaration of:
fun setupViewPager(viewPager: ViewPager): Unit {
you have said that the argument for viewPager cannot be null. If it were, it would be viewPager: ViewPager?. So, if you pass something that could be null into it, you will get a compile error.
What Kotlin is telling you is that in between the lines:
mViewPager = findViewById(R.id.viewpager1)
and
setupViewPager(mViewPager)
something - imagine another method on another thread - could potentially have changed the value of mViewPager from that assigned instance to null. Therefore it's not safe to pass it in.
The only way to solve this without changing the method is supply a value that is guaranteed to be non-null. There are a few ways you could do that:
assign your value to a method-level variable that can't be interfered with, and supply that as the argument
only call your function if the value is non-null, e.g. mViewPager?.let{ pager -> setupViewPager(pager)}
assert that mViewPager will not be null, leaving any violations to fail at runtime, e.g. setupViewPager(mViewPager!!)