关键词汇
MvvM:MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。
material design:
liveData
dataBinding
underscore:下划线
文章
视频
Reso Coder订阅号-Data Binding with LiveData (Two-way & One-way) - Android Kotlin Tutorial
git clone https://github.com/ResoCoder/databinding-with-livedata-tutorial.git
前言
managing the layout programmatically can be quite time consuming and verbose on Android. even though Kotlin spares you calling findViewById accessing views in the activity code can still get pretty ugly .
when you use data binding you can do most of the UI related stuff right in your XML layouts with liveData data binding becomes even more powerful and simple to use while you could create observable fieds ,even in the past when live data didn't exist this new lifecycle where observable makes your data binding even better and simple.
weclome to Reso coder in this tutorial you are going to learn how to use one-way and two-way data binding with liveData and also model view viewModel architecture ,and you and learn more about Mvvm by clicking on the card in the corner .
app 功能介绍
in this particular tutorial we are gonna build this simple app which has only one activity ,but it has one-way and two-way data ,so when we press on this change fruit(按钮-改变水果) we are change the fruit name in this textview above ,but we aren't doing that directly we are doing that all through dataBinding and liveData,so this textview is only set once the liveData changes ,so we are observing for the changes in liveData and then we are updating the textview all throught the data binding ,then over here we have two-way dataBinding ,[输入框]so we can enter a fruit name or some other names .
for example cherry[输入 ]and you can see that as soon as we start typing we have a toast[吐丝] down here which is going to say cherry. because or cherryz because I have the wrong keyboard right now I have quartz instead of QWERTY keyboard,because this is a two-way data binding .
so whatever we write over here it gets automatically reflected in the liveData the underlying liveData for the two-way data binding and whenever the live data change we are displaying a toast and the toast is diaplayed for every change .
so that's why you can see every single letter that's changed since the last time and then we can take this text and display it below so we are taking edit text content and displaying it in this text view below .
and we can also select random fruit [editText-随机选择水果] for editText and yes so that's how to way data binding works the user can edit it and it's gonna be reflected in the data of the app and also we can modify the data of the app and it's gonna be reflected in the UI which user can see that's why it's called two-way dataBinding .
开始创建项目
alright so now let's create a new android project.
we're gonna start with empty activity and we're gonna call this DataBinding(Acitivity) and we don't need to include navigation controller ,but we can do that it's not going to hurt and if you want to learn more about this navigation controller you can check out the video by clicking on the card in corner ,also make sure that you have the Kotlin as the language .
aright as the first thing let's close all of the files which are open now ,we're gonna come back to most of them later and let's go to Gradle.build script and then build a Gradle for the module app .
and we need to implement some plugins first we are probably the version of outdated libraries ,so just hit alt enter as you can also see above here show intention actions with alt enter and we're gonna change the app compat to our ro2 that's just a good practice to do whenever you are starting a new project to immediately update all of your plugins to the latest versions.
now I'm going to paste one line which you also need to have over here for one implementation of a plugin and it's android arch lifecycle extension and if you don't want to type this in here you can get the code from the link in the video description which is gonna take you resea code or comm then I am also going to paste library which is the material .
if you want to learn more about the so called material design 2.0 you can check out my video by clicking on the cart in the corner and this is not really mandatory.
it's just that you can look on something nice and not just some bland old materail design but some new exciting material design with rounded corners and all of that now i it's probably a good idea to hit the sing Now button in the corner .
now we can close the build our Gradle and let's open up res layout and then activity_main.xml. we are going to go over to the text tab and we are basically going to delete inside here ,because we're gonna write something else because we are gonna use data binding
and also you need to have at least 3.2 version of the Android studio so if you have some lower version like 3.1 just upgrade to the newest version .me personally I am using Android Studio 3.3 Canora version but if you have anything later than 3.2 you should be good to go with liveData and dataBinding together
decause we are using data binding we cannot have just the regular old layout we actually need to start the layout with attack called and then we are gonna give this layout tag these following attributes so again you can get the code from the link in the video description and data binding is all about putting more stuff inside the layout of the activity and not not inside activity class.
so we need to be able to communicate with some kind of data for our app because we are using the mvvm architecture[设计模式] in this tutorial and if you wanna check out what mvvm is all about you can check out the video by clicking on the card in the corner.
修改activity_main.xml
but we need to communicate with something inside our code we need tu put a data tag over hever and inside this data tag we are gonna have all of our variables which we can use inside this layout and in our case the name of our variable will be a viewModel just like this and its type is going to be a type which is not currently present but it's going to be called com.resocoder. databinding and it's going to be MainViewModel.
now it's gonna give us an errror ,but we are gonna fix ti pretty soon so this is the viewModel which we will be using for our data in this layout .
now comes the part that you are probably familiar with and that is the actual constrain layout so we're gonno put it inside here ,its which will be match_parent and the height will be also match_parent .
and the tools context for this layout will be MainActivity .now I'm gonna start pasting the individual views inside of this layout because there is no point in writing them out manually ,because that will be bring you to craziness .
so we have the first textview .it's id textView and about that Id,we aren't gonna use this Id in our code this is just here ,so that our views can be anchors to it in this layout because we using the constraint layout and constrain layout needs some archors for our views .
right as you probably know what's really important here is that the text is set to be bound to the viewmodel.currentRandomFruitName and don't worry we are gonna create this viewmodel in just a little while basically what this does is that this is by the way liveData so currentRandomFruitName is liveData and whenever the liveData currentRandomFruitName changes its value is going to be applied to the text property of this textview .
so whenever our fruit name changes the text will also change that's it .that's all there is to this textview .
then next up we will have a button pay attention to this conclick property of our button it says that whenever we click on that button we are going to call viewmodel that onChangeRandomFruitClick function .
this is how you call something inside your databinding layouts we are again using the same syntax so again add "@{表达式}" and then curly braces but instead of doing something like this where we have this viewmodel and the name of the property inside the viewmodel we are using brace then arrow [->]and then the name of the function.
and I've just noticed that this button doesn't look great so let's go over to values styles and let's actually set the theme to now be from appcompat but rather material components and we're gonna save it .
and now when we come back the layout or actually the button should look much much better .now let's paste in another view and this is the EditText which will participate in the two-way data binding dan the the two-way data-binding is signified by this equal sign after the @ symbol as you can see here there is only the add in the onclick above in the textview for the randomFruitName .
there is also just add symbol but here in the edittext beacuse we are using two-way data-binding we have add an equals the two-way data-binding works just as I described previously.
so basically when the user types something inside this eidtText the underlying edit content property which is liveData will be changed and also when this editText content is changed inside our code ,it's going to be reflected the change is gonna be reflected in this textView .that's why it's called two-way data-binding .
after this textview we have another button and this one button is for displaying editText content so it's gonna take whatever is inside this editText ,but actually it's not gonna take it from the editText ,it's rather going to take this value from the editText content property which we will have on the viewModel we aren't directly
operating with the lay out views we are operating with their properties with their underlying properties inside the view model and those properties are represented as live data .
so we aren't pulling anything directly from UI we let the UI change our data inside our view models that's the powerful thing about using data-binding .so basically whenever we click this last button it's going to display the whatever is the content of the editText below inside the textView which we are gonna have in just a bit .
so this is the textView which will have the the value displayed whatever is inside this editText and then lastly we will have the last button which is also the last view inside this layout file .
so here it is and this button will be for selecting a random fruit and displaying it inside this eidtText just like we select random fruit with the first button and display it in the textView above .
so this is gonna do the same thing but it's going to display the random fruit value inside our editText and again we are not gonna directly modify the text of the editText but we are gonna modify the editText content liveData to change also the value that that is displayed inside this editText .
all right with the activity_main.xml done.
开始编码-mvvm
模拟后端
let's create the back end for our app .because what kind of an app would this be if it didn't have a back end yes I know that this is only a tutorial and that's why we are gonna have a fake back end and we are gonna create this back end in hen form of a repository .
and if you don't know what a repository is it's the single source of truth for our view models and really if you don't know what that is you should probably check out my tutorial on mvvm where I explain it to the full extent .
FakeRepository.kt
object FakeRepository {
//模拟数据库
private val fruitNames: List = listOf(
"Apple", "Banana", "Orange", "Kiwi", "Grapes", "Fig",
"Pear", "Strawberry", "Gooseberry", "Raspberry"
)
private val _currentRandomFruitName = MutableLiveData()
//暴露public属性,因为MutableLiveData可以被修改
val currentRandomFruitName: LiveData
get() = _currentRandomFruitName
init {
_currentRandomFruitName.value = fruitNames.first()
}
fun getRandomFruitName(): String {
val random = Random()
return fruitNames[random.nextInt(fruitNames.size)]
}
fun changeCurrentRandomFruitName() {
_currentRandomFruitName.value = getRandomFruitName()
}
}
so now let's create a new Kotlin class and its name will or actually not class it's gonna be an object because there can be only one in of our repository and its name wil be simply FakeRepository .
inside this fake repository we are gonna have a private value through names which is gonna look something like this [fruitNames].so we have apple 、banana 、orange and all of that stuff this is only a simple list of strings and this is something that is representing a fake real database because we aren't using any like room database or sequal eye database room is basically a layer on top of sequel light we are using firebase here .
we are just using simplest because we want to keep this simple and again you can get the code from the link in the video description .so if you wanna check this code out on your own pace you can do so by going to Reso-codr or calm.
now we're gonna have private value _currentRandomFruitName and it's gonna start with underscore and it's gonna be equal to ImmutableLiveData of type String ,then this is only private and aslo let's instantiate this MutableLiveData .
and because and because this _currentRandomFruitName is private ,we also need to have a public val currentRandomFruitName[对外暴露],and why are doing this is because we don't want to expose MutableLiveData to outside classes because MutableLiveData value can be changed. we only want to expose liveData which value cannot be chaged .
so only want to change the value of our mutableLiveData from within the FakeResipository object and not from outside. this is really good practice to have to do some imformation hiding and encapsulation [封装],so liveData this is of type of liveData
and is going to be automatically cast to liveData instead of mutable like that this is the underlying _currentRandomFruitName and as you can see this is what we are gonna use the first textView which will display the random fruit. yeah,it's not directly from the repository but rather from a viewModel .
but the viewModel will simply only get the data from our repository .so once we have the repository created creating the viewModel will go like a breeze .
now let's go back to the FakeRepository and let's create an init block which is something basically like a construcotr[类似构造函数]. so whenever the favorite posit or instance is created this init block will run and we want to set our mutableLiveData _currentRandomFruitName to be equal to fruitnames and we want to get the first first which is Apple .
so this is just setting up our _currentRandomFruitName .
and we also want to have a function for getRandomFruitName which is going to return a string and basically we are just going to create a random so java util instantiate it and we are going to simply return fruit names .
and we want to get the random fruit name at index which is random that nextInt and we want to set the upper bound to be fruit names that size ,so that we don't get any invalid array index exceptions.
we're gonna use this function inside this fake repository in the next function that we are going to write here and also in the viewModel directly .so this is really just some helper function this getRandomFruitName .
and then the last funciton inside this repository will be changeCurrentRandomFruitName and this will simply get the _currentRandomFruitName and set its value to be equal to getRandomFruitName.
class MainViewModel : ViewModel() {
val currentRandomFruitName: LiveData
get() = FakeRepository.currentRandomFruitName
fun onChangeRandomFruitClick() = FakeRepository.changeCurrentRandomFruitName()
@Bindable
val editTextContent = MutableLiveData()
private val _displayedEditTextContent = MutableLiveData()
val displayedEditTextContent: LiveData
get() = _displayedEditTextContent
fun onDisplayEditTextContentClick() {
_displayedEditTextContent.value = editTextContent.value
}
fun onSelectRandomEditTextFruit() {
editTextContent.value = FakeRepository.getRandomFruitName()
}
}
and that's it now let's create main viewModel which is going to be a class .so new Kotlin file or class is gonna be a class and his name will be mainViewModel .it's gonna inherit from viewModel so let's important the viewModel from the proper package and we're gonna have currentRandomFruitName .even in this viewModel
which will be of type liveData
we also want to have a function which will handle the button click which signal that we should change the random fruit so fun onChangeRandomFruitClick and this will simply call FakeRepository that changeCurrentRandomFruitName. and these are the properties and also the function which is referenced inside our main_activity layout file .
so this is the property currentRandomFruitName for the first textView and then this first has this onClick which calls onChangeRandomFruitClick which is precisely the function which we have just created inside our viewModel .
so let's go back to our viewModel and now we are going to add a property for two-way data-binding .so let's add first and then I'm going to explain what it all does so editTextContent it's equal to mutableLiveData of type string and also instantiate this mutableLiveData and because this is userd for two-way data-binding .we need to have an attribute that says that this editTextContent property is bindable .
and wow I have just realized that I have forgotten about something really important insdie the build our gradle.built that is no big deal because we can always change it . so right inside Android all the way down we need to add one thing here which is data binding and set it to be enabled by setting the enable to be true and now what we are going to do is hit the sync now .
and after all of this is synced we should be able to use the attribute inside main viewModel so this add bindable will be actually working .and yes we can now hit alt+enter and import the bindable attribute and all is nice and fine .
so this is the value used for two-way data-binding and as I am saying probably already for the third time when the value changes in the code it will be shown on the screen when the user changes the value which is written inside the UI it's also going to be changed inside this property .
and because we are using this property for two-way data-binding we need to expose it as mutableLiveData . because otherwise the layout won't be able to access the value property of mutableLiveData which is sad that we really need to expose mutableLiveData .
but hey if it's gonna save us some time and effort why not . however if you aren't using two-way data-binding ,I would always suggest that you don't expose mutableLiveData directly ,but that you only expose liveData to outside classes ,because that is going to make your code much much safer .
all right now let's create another pair of properties for displayed editTextContent ,so we are again going to have only a priave mutableLiveData so _displayedEditTextContent and it's gonna be equal to mutableLiveData of type string and instance it over here and then we are gonna have a public displayedEditTextContent so let's just copy and paste the name in here and it's going to be of type liveData also string if I could spell and it's going to only have a getter just lkie what we have insie the repository this is the same concept that we are only exposing the liveData and the getter will simply return _displayedEditTextContent this is the liveData.
they will reflect whatever is inside the editTextContent when we press the button here this display editTextContext button when this is pressed the value of our _displayedEditTextContent property will be changed and the value will be changed right inside function which name is onDisplayEditTextContentClick and whenever the button is clicked we are smiply gonna set the value of display at _displayedEditTextContent value tobe equal to editTextContent.value.
so we only set the value here and data-binding takes care of showing this new value in the UI this is the function which is called from our data-bindings layout from this button here so viewModel on display editTextContentClick .
and again you can get the code from the link in the video description and examine it more through offl
and thne last function function which is for the last button inside our layout which simply takes some value some random fruit value and it sets it to be displayed inside our editText by changing the editTextContent .this function name is onSelectRandomEditTextFruit .and is going to select what is says so we are gonna say editTextContent and that value and it's gonna be equal to FakeRepository FakeRepository.getRandomFruitName()
alright and now the last thing which we need to do to get this working is to go to our main_activtiy and and over here we need to change something .
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainViewModel = ViewModelProviders.of(this)
.get(MainViewModel::class.java)
DataBindingUtil.setContentView(
this, R.layout.activity_main
).apply {
this.setLifecycleOwner(this@MainActivity)
this.viewmodel = mainViewModel
}
mainViewModel.editTextContent.observe(this, Observer {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
})
}
}
so first up we need to get our main viewModel from ViewModelProvider and again check out my fully fledged video on mvvm if you want to learn more about this stuff so val mainViewModel it's gonna be equal to ViewModelProviders and we want to get of() and of this activity so this and from viewModelProvider we want to get a ViewModel for the class MianViewModel .so MainViewModel class java .
and this eighter get or create the instance of MainViewModel so next up because we are using data-binding we need to have some data-binding specific ,we are gonna call dataBindingUtil and this dataBindingUitl we want to set contentView .
we want to set it for ActivityMainBinding and this is an automatically generated class .
because every layout that we have which has this layout property and data properties
========26:48 ==32:04