I am building an app on MVVM+Kotlin+Databinding, and i have this situation i am stuck at.
I have LoginFragment which has a phone number edittext and a button,
Now i need to check if the phone number is empty or not when user clicks the button.
Normally i would do that by using this code in my fragment.
if(!binding!!.phone.text.isEmpty()) {
//do something
}
But according to experts my view should not know anything about the business logic, Hence i need to have this check inside my viewModel.
so what should be the best way to achieve this?
Here is the bet practice to achieve that (from my point of view):
In your layout add text watcher and text to your EditText
android:text="#{view_model.phone}"
app:addTextChangedListener="#{view_model.phoneWatcher}"
and on click method to your button
android:onClick="#{() -> view_model.save()}"
Inside the ViewModel you will have text observable and a watcher
val phone = ObservableField<String?>()
val phoneWatcher = object : TextWatcherAdapter() {
override fun afterTextChanged(s: Editable?) {
phone.set(s?.toString())
}
}
Now you can make your check inside ViewModel
fun save() {
if (phone.get()?.isNotEmpty == true) {
// TODO: do something
}
}
Also please note that it is a best practice to avoid doing something like that binding!!.phone in Kotlin. If you're using !! to make a possible nullable object look like it is not-nullable (even if you're 100% sure it is) - you're doing something wrong.
Related
I have a BaseViewModel that basically has the function to get the user data like so:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun getUserData() = viewModelScope.launch {
_userResponse.value = Resource.Loading
_userResponse.value = repository.getLoggedInUserData()
}
}
In my Fragment, I access this data by just calling viewModel.getUserData(). This works. However, I'd like to now be able to edit the data. For example, the data class of UserResponse looks like this:
data class UserResponse(
var id: Int,
var username: String,
var email: String
)
In other fragments, I'd like to edit username and email for example. How do I do access the UserResponse object and edit it? Is this a good way of doing things? The getUserData should be accessed everywhere and that is why I'm including it in the abstract BaseViewModel. Whenever the UserResponse is null, I do the following check:
if (viewModel.userResponse.value == null) {
viewModel.getUserData()
}
If you want to be able to edit the data in userResponse, really what you're talking about is changing the value it holds, right? The best way to do that is through the ViewModel itself:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun setUserResponse(response: UserResponse) {
_userResponse.value = response
}
...
}
This has a few advantages - first, the view model is responsible for holding and managing the data, and provides an interface for reading, observing, and updating it. Rather than having lots of places where the data is manipulated, those places just call this one function instead. That makes it a lot easier to change things later, if you need to - the code that calls the function might not need to change at all!
This also means that you can expand the update logic more easily, since it's all centralised in the VM. Need to write the new value to a SavedStateHandle, so it's not lost if the app goes to the background? Just throw that in the update function. Maybe persist it to a database? Throw that in. None of the callers need to know what's happening in there
The other advantage is you're actually setting a new value on the LiveData, which means your update behaviour is consistent and predictable. If the user response changes (either a whole new one, or a change to the current one) then everything observeing that LiveData sees the update, and can decide what to do with it. It's less brittle than this idea that one change to the current response is "new" and another change is "an update" and observers will only care about one of those and don't need to be notified of the other. Consistency in how changes are handled will avoid bugs being introduced later, and just make it easier to reason about what's going on
There's nothing stopping you from updating the properties of the object held in userResponse, just like there's nothing stopping you from holding a List in a LiveData, and adding elements to that list. Everything with a reference to that object will see the new data, but only if they look at it. The point of LiveData and the observer pattern is to push updates to observers, so they can react to changes (like, say, updating text displayed in a UI). If you change one of the vars in that data class, how are you going to make sure everything that needs to see those changes definitely sees them? How can you ensure that will always happen, as the app gets developed, possibly by other people? The observer pattern is about simplifying that logic - update happens, observers are notified, the end
If you are going to do things this way, then I'd still recommend putting an update function in your VM, and let that update the vars. You get the same benefits - centralising the logic, enabling things like persistence if it ever becomes necessary, etc. It could be as simple as
fun setUserResponse(response: UserResponse) {
_userResponse.value?.run {
id = response.id
username = response.username
email = response.email
}
}
and if you do decide to go with the full observer pattern for all changes later, everything is already calling the function the right way, no need for changes there. Or you could just make separate updateEmail(email: String) etc functions, whatever you want to do. But putting all that logic in the VM is a good idea, it's kinda what it's there for
Oh and you access that object through userResponse.value if you want to poke at it - but like I said, better to do that inside a function in the VM, keep that implementation detail, null-safety etc in one place, so callers don't need to mess with it
The ideal way to update userResponse you should change/edit _userResponse so that your userResponse we'll give you the updated data.
it should be something like this
_userResponse.value = Resource<UserResponse>()
i am new to Android development with Kotlin pogramming language, i am not able to understand this code below,
what i am guessing is that an instance(scanResultAdapter) is created from ScanResultAdapter class, this class has the code for recyclerView adapter.
This code is in MainActivity.kt file in punchThrough's article about BLE -
private val scanResultAdapter: ScanResultAdapter by lazy {
ScanResultAdapter(scanResults) { result ->
// User tapped on a scan result
if (isScanning) {
stopBleScan()
}
with(result.device) {
Log.w("ScanResultAdapter", "Connecting to $address")
connectGatt(context, false, gattCallback)
}
}
}
Which point you don't understand?
Regards your guess(the logic of code), that's right, I think.
It seems that you are not familiar with Kotlin, you can check them one by one. For an instance, I don't know this style code:
ScanResultAdapter(scanResults) { result ->
....
}
{ result -> .. }, I need to know that's the Lambdas Expressions
Here's a reference
Maybe I don't understand the with of Kotlin, refer to this document and so on.
You are right. (scanResults) is the first argument of constructor. Second is callback of click listener in curly brackets. When user tap on a device in the list this code in braces {} will be executed. Kotlin can take out of parentheses () arguments if it is functions.
I am working with overload functions in Kotlin.
In this schematic example, suppose a function whose only difference is the type of view that I pass to the function. One uses TextView, the other uses Button, so I have 2 different functions.
fun workWithViews(v:TextView,...){
// code
}
fun workWithViews(v:Button,...){
// same code!
}
In this case, the properties I use are the same (isAllCaps, gravity, etc.). The problem is that I have to place the same code twice, i.e., the whole code is exactly the same.
It happens because isAllCaps (just like many other properties) it is not a general property of a view, but of some types of views
So it doesn't work, because obviously the compiler see the function parameter, not the real parameter.
I also can make function with a view type parameter, with a big when with my type possibilities:
fun workWithTextView(v:View,...){
when{
(v is TextView) -> {
// code
}
(v is Button) {
// same code
}
} // when
}
The 2 solutions are terrible and generate duplicate code or boilerplate.
I can also do the when before each access to some field, which makes things even worse. Now imagine if one has 5 similar types instead of 2, with many fields in common.
I read some suggestions to create union types in Kotlin. It would be great!
For instance:
fun workWithViews(v:(TextView, Button),...){
// just one code repetition....
}
or
union textBut = TextView , Button
fun workWithViews(v:textBut ,...){
// just one code repetition....
}
In that case I would only have to test a certain type (if (v is typeX)) if I used something specific for that type.
Is there some best solution?
Button is a subclass of TextView, so you can make the function signature take a TextView and put Button-specific stuff in an if-block.
fun workWithTextView(textView: TextView) {
// Do stuff common to TextViews and Buttons.
if (textView is Button) {
// Do extra stuff only for Buttons.
}
}
If Views have common methods, then most likely one view extends the other.
For example, Button extends TextView and you can do this:
fun workWithViews(v:TextView){
// TextView code
}
fun workWithViews(v:Button){
// Button specific code
workWithViews(v as TextView)
}
I am developing an Android application using Kotlin programming language. I am starting to use the Model-View-Presenter pattern for my application. To be honest, this is my first time using the MVP pattern even though I have been doing Android Development quite a little while. Now, I am a little bit struggling following the best practices and standards of the pattern.
This is the feature now I am currently developing. A very simple and basic feature.
The application will update a text view. When the text view is updated, a string (message) will be passed to a function as a parameter. Inside the function, if the string is empty, it will hide the text view. Otherwise, it will show the text view again. My struggle is that I am not quite sure how to implement my code that fits the MVP pattern. Especially, the logic to toggle the visibility state of the text view.
This is the current signature of my presenter class called, LoginActivityPresenter
class LoginActivityPresenter(viewArg: View)
{
private var view:View = viewArg
interface View
{
fun updateErrorMessageTextView(message: String)
}
}
What I am thinking is that the activity class will implement the LoginActivityPresenter.View and provide the implementation of the updateErrorMessageTextView method. So in the activity, inside one single function, it will do the following tasks.
It will check if the string is empty. If yes, it will hide the view. Otherwise, it will show the view (back). Then it will set the text view with the message. All in the activity class because of the above presenter class signature.
The other way I am thinking of implementing LoginActivityPresenter class is as follows.
class LoginActivityPresenter(viewArg: View)
{
private var view:View = viewArg
fun updateErrorMessage(message: String) {
if (message.isNullOrEmpty()) {
this.view.hideErrorTextView()
return
}
this.view.showErrorTextView()
this.view.updateErrorMessageTextView(message)
}
interface View
{
fun updateErrorMessageTextView(message: String)
fun showErrorTextView()
fun hideErrorTextView()
}
}
So in the second implementation, the presenter class will handle the logic of showing or hiding the view based on the string value rather than logic is being handled by the activity class.
My question is that which approach is more suitable and standard way for the MVP pattern and why?
This is written in kotlin but I'm pretty sure it's almost the same in java.
something.setOnClickListener {
availableReportRecycler.isActivated = !availableReportRecycler.isActivated
if (availableReportRecycler.isActivated) availableReportRecycler.visibility = View.VISIBLE
else availableReportRecycler.visibility = View.GONE
}
Is there a way in kotlin/java to simplify this code?
I feel like it should be able to reduce it to something like (pseudo code):
something.setOnClickListener {
availableReportRecycler.visibility =
{availableReportRecycler.isActivated = !self} ? View.VISIBLE:View.GONE
}
I've brute forced all kinds of combinations after not finding anything relevant online and still cant get it to work.
How beautiful can you make it?
Best answer gets a virtual beer and an "I'm alright" sticker!
The primary part of the presented code that is far from being pretty is the repetition of the long availableReportRecycler name. Combining scoping function with from the Kotlin standard library with imports and if expression lets us make this code much cleaner without making it harder to understand:
something.setOnClickListener {
with (availableReportRecycler) {
isActivated = !isActivated
visibility = if (isActivated) VISIBLE else GONE
}
}
You can apply further scoping function like also to avoid repeating isActivated on the second statement, but, for my taste, it would not make this particular code better.
I think Roman Elizarov has a good approach. But if you want to link activated and visibility
you might want to consider overriding the setActivated like this:
class AvailableReportRecycler(context: Context) : View(context) {
override fun setActivated(value : Boolean) {
super.setActivated(value)
visibility = if(value) View.VISIBLE else View.GONE
}
}
This way you link the visibility to the state of activated. and you can call it like this:
something.setOnClickListener {
availableReportRecycler.isActivated = !availableReportRecycler.isActivated
}
Isn't object-orientation beautiful? :)