Just starting out with Android development? Then this article is for you.
刚开始进行Android开发? 然后,本文适合您。
In this article, I’ll be discussing what’s new in android, the path I think you should follow, design patterns, good practices, and some dos and don’ts, so brace up let’s take this one after the other. I’ll be starting with MVVM.
在本文中,我将讨论android中的新增功能,我认为您应该遵循的路径,设计模式,良好实践以及一些需要注意的地方,因此,让我们继续进行讨论。 我将从MVVM开始。
MVVM
MVVM
Model-View-View-Model is an architectural pattern that separates your business logic from UI code. Let’s take this bit by bit.
Model-View-View-Model是一种架构模式,可将您的业务逻辑与UI代码分开。 让我们一点一点地看。
Model: Model represents your data holder, say something like a data class that is used to receive/hold data, say for example a User model used to receive data from a local database or server.
模型:模型代表您的数据持有者,例如用于接收/保存数据的数据类之类的数据,例如用于从本地数据库或服务器接收数据的用户模型。
View: View represents the visual elements on the screen such as activity, fragment, buttons e.t.c
视图:视图代表屏幕上的可视元素,例如活动,片段,按钮等
View Model: ViewModel
is used to drive the UI basically. Observe the image below and let’s connect the dots together (to make whatever shape lol )
视图模型: ViewModel
用于基本上驱动UI。 观察下面的图片,让我们将点连接在一起(使形状变为大声笑)
The essence of ViewModel
is to facilitate separation of concern. Separation of concern makes you write clean and good code, and it makes your code become less painful. So considering the image above, notice the arrows connected to each segment, that is how data would flow through the app. Let’s start with the repository part.
ViewModel
的本质是促进关注点分离。 关注点的分离使您可以编写干净且良好的代码,并且可以减轻代码的痛苦。 因此,考虑上图,请注意连接到每个细分市场的箭头,这就是数据如何流经应用程序的方式。 让我们从存储库部分开始。
The repository is a core data source class that determines/decides whether to fetch data from the local DB or server. Ideally you should always follow the single source of truth pattern.
存储库是一个核心数据源类,它确定/决定是从本地数据库还是从服务器获取数据。 理想情况下,您应该始终遵循真理模式的单一来源。
The diagram above is explanatory but let’s talk about it, the beginning says only fetch your data from the local database and dispatch the value/data to your UI (in this case the ViewModel
), whether it is empty or not always ask for a refresh from the server to get the latest data (although you shouldn’t always fetch data from the internet all the time, you can use this rate limiter idea on the google github sample ap).
上图是说明性的,但让我们来谈谈,开始时只说从本地数据库中获取数据,然后将值/数据分派到您的UI(在本例中为ViewModel
),不管它是否为空还是总是要求刷新从服务器获取最新数据(尽管您不应该一直从互联网上获取数据,但可以在Google github示例ap上使用此速率限制器想法)。
Once the data is gotten, do not directly dispatch that data to the UI, instead save the data to the local database, which your UI should respond to automatically. You should persist as much data as you can, and persist only what needs to be persisted, Room offers the benefit of offline capability which is very cool for the user experience, however, there are some things you just want to directly dispatch to the UI, functions like login, sign up e.t.c and therefore doesn’t need persistence. So basically your repository does all this work. So ViewModel
is like a driver for your UI, it’s the supplier of data to the view (activity/fragment as described in the image above). The ViewModel
relies on the repository for data. So how does the ViewModel
communicate with the UI, it does this by using LiveData
. LiveData
is a data holder class that can be observed within a given lifecycle and it helps with configuration changes.
获取数据后,请勿直接将数据分发到UI,而应将数据保存到本地数据库,您的UI应该会自动响应。 您应该尽可能多地保留数据,并且仅保留需要保留的数据。Room提供了脱机功能的好处,这对于用户体验来说非常酷,但是,有些事情您只想直接分派到UI ,登录,注册等功能,因此不需要持久性。 因此,基本上您的存储库可以完成所有这些工作。 因此, ViewModel
就像您的UI的驱动程序一样,它是视图数据的提供者(如上图所示,活动/片段)。 ViewModel
依赖于存储库中的数据。 因此, ViewModel
如何与UI通信,它通过使用LiveData
来LiveData
。 LiveData
是可以在给定生命周期内观察到的数据持有者类,它有助于更改配置。
LiveData
can be used in two ways; switchMap()
and map()
, switchMap()
applies a function to the value stored in the LiveData
object and unwraps and dispatches the result downstream. The function passed to switchMap()
while map()
transforms data to a LiveData
. If you do a quick scroll to the code snippet below, you would see that switchMap
returns a LiveData
from userRepo.getUser()
while map also returns a LiveData
, but you can see the function getFullName
doesn’t return LiveData
, it just returns a String
value and then the map
transforms it to LiveData
for it to be observed by the UI.
LiveData
可以通过两种方式使用: switchMap()
和map()
, switchMap()
将函数应用于存储在LiveData
对象中的值,然后解包并向下游分派结果。 该函数传递给 switchMap()
而map()
将数据转换为LiveData
。 如果你做一个快速滚动到下面的代码片段,你会看到switchMap
返回LiveData
从userRepo.getUser()
而地图还返回LiveData
,但你可以看到功能getFullName
不返回LiveData
,它只是返回String
值,然后map
将其转换为LiveData
,以供UI观察。
private val userId = MutableLiveData()
val userDetails = userId.switchMap {
userRepo.getUser(it)
}
val userFullName = userDetails.map {
getFullName(it.firstName, it.lastName)
}
fun setUserId(userId: String) {
this.userId.value = userId
}
fun getFullName(firstName: String, lastName: String) = "$firstName $lastName"
Do not reference your UI from the ViewModel
because it could leak data. Your UI(activity/fragment) might lose data when a configuration change occurs, ViewModel
will retain data to an extent, this is why you should keep your data in the ViewModel.
However if the app is killed in the background ViewModel
would lose its data. See this link for more about UI state and ViewModel
.
不要从ViewModel
引用您的UI,因为它可能会泄漏数据。 配置发生更改时,您的UI(活动/碎片)可能会丢失数据, ViewModel
将在一定程度上保留数据,这就是为什么您应该将数据保留在ViewModel.
但是,如果该应用在后台被杀死, ViewModel
将丢失其数据。 有关UI状态和ViewModel
更多信息,请参ViewModel
链接 。
Don’t do too much in the ViewModel
, it wasn’t made for you to bloat it with logics, instead delegate the logics to another class, like a util class, especially plain java/kotlin codes. For example FileUtil to store, retrieve, delete or create files, codes like sorting list, reordering, loops, and all those things that don’t really require view, it makes testing a lot easier and sweet. Also do not expose MutableLiveData
to your view, make MutableLiveData
private, and expose a LiveData
to your view like you see in the above picture.
在ViewModel
不要做太多事情,并不是让您使用逻辑来夸大它,而是将逻辑委托给另一个类,例如util类,尤其是简单的Java / kotlin代码。 例如,用于存储,检索,删除或创建文件的FileUtil,诸如排序列表,重新排序,循环之类的代码以及所有实际上不需要查看的内容,它使测试变得更加轻松和甜蜜。 也不要暴露MutableLiveData
到您的视图,使MutableLiveData
私人和暴露LiveData
到您的视图就像你在上面的图片中看到。
So how then do we set a value to a MutableLiveData
, always use a public method like the setUserId
function in the code snippet above to set a MutableLiveData
like you would in a regular getter-setter class.
因此,我们该如何为MutableLiveData
设置一个值,请像上面的常规getter-setter类一样,始终使用公共方法(如上面的代码段中的setUserId
函数)来设置MutableLiveData
。
Another take on ViewModel
is as long as you make it the driver for your UI, all variables also don’t have to be LiveData
, mostly LiveData
is best used for UI changes; observe something and then react to that change. If it isn’t doing any of that then you could just declare it as a normal variable and you could still reference them from the UI say something like viewModel.status
. I recommend this read to know more about LiveData
ViewModel
另一种用途是,只要您使其成为UI的驱动程序,所有变量也不必都是LiveData
,大多数情况下, LiveData
最好用于UI更改。 观察一些东西,然后对这种变化做出React。 如果它没有执行任何操作,则可以将其声明为普通变量,并且仍然可以从UI引用它们,例如说viewModel.status
。 我建议阅读 LiveData
以了解有关LiveData
更多信息
So let’s talk about the view (Activity/Fragment), it is the end consumer of our data, according to our first diagram, you see that it gets data from the ViewModel
, and it has only one responsibility, guess what that is, to display data in a manner that a user can interpret and understand, say your recyclerview, buttons, TextView and so on, it is what the activity/fragment is meant to do, so why would you want to do otherwise, remember our SOC, make your code simple as possible. So how do we get data from ViewModel
, say hello to Observer
, yes you observe your data from ViewModel
and display them accordingly. For example;
因此,让我们谈谈视图 (活动性/片段),它是我们数据的最终使用者,根据我们的第一个图表,您看到它是从ViewModel
获取数据的,并且它只有一个责任,猜测是什么,以用户可以理解和理解的方式显示数据,例如说您的recyclerview,按钮,TextView等,这就是活动/片段的目的,因此,为什么要这样做,请记住我们的SOC,您的代码尽可能简单。 那么我们如何从ViewModel
获取数据,向Observer
打招呼,是的,您可以从ViewModel
观察数据并相应地显示它们。 例如;
viewModel.userFullName.observe(viewLifeCycleOwner, Observer{ userFullNameView.text = it })
viewModel.userFullName.observe(viewLifeCycleOwner, Observer{ userFullNameView.text = it })
So let the view operations stay in view, including operations like listeners, adapters, setText
, and so on. Now let’s talk about events in an activity, when you want to show toast messages, snack messages, or navigate to another activity or fragment, how then do we do this, since our activity/fragment takes order from our ViewModel
, well, you use a class called EventObserver
, it handles a change once i.e it observes only once, and subsequent calls to onChange()
method won’t dispatch/observe any changes, this is the way to go with events because LiveData
is known to always dispatch/observe previous data and it wouldn’t make sense when you navigate to another screen via LiveData
and when you press back it fires again which is a bad experience, so to prevent that you use EventObserver
.
因此,让视图操作保持在视图中,包括诸如侦听器,适配器, setText
等操作。 现在,让我们谈谈活动中的事件,当您想要显示吐司消息,小吃消息或导航到另一个活动或片段时,我们该怎么做,因为我们的活动/片段需要从ViewModel
进行排序,所以您可以使用一个名为EventObserver
的类,它处理一次更改,即仅观察一次,并且随后对onChange()
方法的调用将不会分派/观察任何更改,这是处理事件的方法,因为已知LiveData
总是分派/观察以前的数据,当您通过LiveData
导航到另一个屏幕时,当您再次按回它时,这将是没有用的,因此这是很糟糕的体验,因此请避免使用EventObserver
。
To learn more about EventObserver
and how it’s been used, check out this link.
要了解有关EventObserver
以及如何使用它的更多信息,请查看此链接 。
RecyclerView + ListAdapter
RecyclerView + ListAdapter
I’d also love to talk about recyclerView, yea so we know recycler view is used to display a list of items. The old way is to use RecyclerView.Adapter
, what is wrong with this? Actually nothing, but it could be better with ListAdapter
. The notorious notifyDataSetChanged()
is what we want to improve, normally you would call this method to populate your recyclerview once, and subsequently you make calls to notifyItemChanged…removed
e.t.c. to update your list whenever your list changes, that is fine, the problem appears when you want to update the list and you call notifyDataSetChanged()
, you shouldn’t do this because it rebinds all the view and the data(the old list and new list you adding ), this is inefficient and it's expensive to do, even google says you should rely on it as a last resort. Another problem again is imagined if you have a long list of items say 100 and you need to update 20 of them, you need to know each position of the item and apply notifyItemChanged(pos)
to each of them, quite expensive and boilerplate code right? So a better way is to use ListAdapter
with your recyclerview.
我也很想谈谈recyclerView,是的,所以我们知道recycler view用于显示项目列表。 旧的方式是使用RecyclerView.Adapter
,这有什么问题? 实际上什么都没有,但是使用ListAdapter
可能会更好。 臭名昭著的notifyDataSetChanged()
是我们要改进的地方,通常您将调用此方法一次来填充您的recyclerview,随后您只要对列表进行更改就调用notifyItemChanged…removed
来更新列表,这很好,就会出现问题当您想更新列表并调用notifyDataSetChanged()
,您不应该这样做,因为它会重新绑定所有视图和数据(您添加的旧列表和新列表),这样效率低下,而且即使这样做也很昂贵谷歌说,你应该依靠它作为最后的手段。 如果您有一长串的项目(例如100)并且需要更新20个项目,那么您会notifyItemChanged(pos)
另一个问题,您需要知道该项目的每个位置,并对每个项目应用notifyItemChanged(pos)
,这非常昂贵且样板代码正确? 因此,更好的方法是在您的recyclerview中使用ListAdapter
。
ListAdapter
is a concept that utilizes an algorithm to calculate the difference between two lists (old list and new list) and dispatches the changes without rebinding all the list, it only updates the item that has changed in a list and adds to it if there’s a new item. Now instead of manually calling notify bla bla bla, ListAdapter
uses DiffUtil
to help you with that, you simply just call submitList(newList) upon the instance of your recyclerview.
ListAdapter
是一个概念,它利用一种算法来计算两个列表(旧列表和新列表)之间的差异,并在不重新绑定所有列表的情况下分派更改,它仅更新列表中已更改的项目,如果有则添加到列表中新物品。 现在, ListAdapter
使用DiffUtil
代替了手动调用notify bla bla bla的工作, ListAdapter
只需在您的recyclerview实例上调用SubmitList(newList)即可 。
submitList
is all you would ever have to call in your activity/fragment, also remember that our data is coming from Room
and when there’s a new list, ListAdapter
does the calculation to find the old list in the adapter and the incoming list and updates it appropriately. When using ListAdapter
with your recyclerview you don’t have to override getItemCount()
it takes care of that for you. This is an example below;
submitList
是您在活动/片段中必须调用的所有内容,还请记住,我们的数据来自Room
并且当有新列表时, ListAdapter
进行计算以在适配器和传入列表中找到旧列表并进行更新适当地。 将ListAdapter
与recyclerview一起使用ListAdapter
,不必重写getItemCount()
它会为您解决这一问题。 这是下面的例子;
class CourseAdapter(private val viewModel: CourseViewModel) :
ListAdapter(CourseDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater.inflate(R.layout.course_item, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(viewModel, getItem(position))
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(viewModel: CourseViewModel, course: Course) {
with(itemView) {
courseTitle.text = course.courseTitle
courseThumbnail.loadImage(course.thumbnail)
modules.text = GeneralUtil.getNumber(course.numberOfModules, "Module")
courseLessons.text = GeneralUtil.getNumber(course.numberOfLessons, "Lesson")
startCourse.setOnClickListener {
viewModel.openCourseModule(
course.id,
course.courseTitle,
course.numberOfLessons
)
}
}
}
}
}
To see full details on ListAdapter
you can also check out this medium post by Craig Russell
要查看ListAdapter
完整详细信息,您还可以查看Craig Russell的这篇中等帖子
Wait just before we move to another section, look at the ViewHolder
and see how simple and neat it is, remember our SOC, recyclerview falls under the category of a view and it basically has only one job, to display a list of items, try as much as possible not to bloat your recycler view with business logic. In the above code snippet look at lines 17 and 18, it would have been a long code containing if and else statements which would become hard to test, if not impossible to test. Also, another thing that I love there is the use of ViewModel
in the recyclerview, remember that ViewModel
is a driver for the UI and we want to try as much as possible to reduce code in the view(activity/fragment), I love this method, it eliminates boilerplate code such as interfaces or lambdas to receive click actions in the view by overriding them in your activity/fragment, which you would probably still re-direct the actions back to the ViewModel
(na double be that ).
等到我们移到另一部分之前,查看ViewHolder
,看看它多么简单和整洁,记住我们的SOC,recyclerview属于视图的类别,它基本上只有一项工作,以显示项目列表,然后尝试尽可能不要用业务逻辑夸大您的回收者视图。 在上面的代码片段中,请看第17和18行,这将是一个很长的代码,其中包含if和else语句,这些语句将变得难以测试,甚至不是不可能测试。 另外,我喜欢的另一件事是在recyclerview中使用ViewModel
,请记住ViewModel
是UI的驱动程序,我们希望尝试尽可能减少视图中的代码(活动/片段),我很喜欢方法中,它通过覆盖活动/片段中的样板代码(例如接口或lambda)以在视图中接收点击动作,从而消除了点击行为,您可能仍会将这些动作重定向回ViewModel
(不是)。
Doing this won’t hurt and it ensures that your activity/fragment is not bloated with unnecessary code.
这样做不会有任何伤害,并且可以确保您的活动/片段不会因不必要的代码而肿。
Now you can even make this even better further by reducing the actions in your ViewModel
with a tool called DataBinding
, although I’m not really a fan of it, used it a few times and it’s good.
现在,您甚至可以通过使用DataBinding
工具减少ViewModel
的动作来进一步改善此效果,尽管我并不真正喜欢它,已经使用了几次,这很好。
DataBinding
数据绑定
DataBinding
is a library that allows you to bind your UI components to a data source, such that it reduces code in your activity/fragment and ViewModel
classes. So let’s see how this is done briefly (Note that this is just a basic introduction, not a full tutorial, you can check out see this free udacity course).
DataBinding
是一个库,它允许您将UI组件绑定到数据源,从而减少活动/片段和ViewModel
类中的代码。 因此,让我们简要地看一下这是如何完成的(请注意,这只是基本介绍,而不是完整的教程,您可以查看此免费的udacity课程 )。
When using DataBinding
you usually wrap your layout with alayout
tag
使用DataBinding
,通常使用layout
标签包装layout
So this layout directly communicates with the ViewModel
with writing less code in the host fragment/activity, you just have to set it up first.
因此,此布局与ViewModel
直接通信,只需在主机片段/活动中编写较少的代码,您只需要首先进行设置即可。
So imagine you have a button and onClick
of the button you want to display a text on a TextView, the ViewModel
and layout would basically look like this;
因此,假设您有一个按钮,并且想要在TextView上显示文本的按钮的onClick
,则ViewModel
和布局基本上是这样的;
//ActivityViewModel.kt
private val _textToShow = MutableLiveData()
val textToShow : LiveData()
get() = textToShow
fun buttonClicked(){
_textToShow.value = "Hello"
}
//layout.xml
So your layout basically binds with the data in your ViewModel
, more like observers. If you observe the code well you see that DataBinding
allows us to declare a ViewModel
in the layout, with this we can trigger any function in the ViewModel
and directly propagate the data in our view like you see in line 37, that’s the power of DataBinding
. It also helps eliminate boilerplate observers in your activity/fragment, so this brings us back to the recycler view again, no more click listeners in your adapter, you can now put them in your layout item, note that you can use the same ViewModel
as the host fragment/activity, so your layout fires an action, your ViewModel
receives it and the layout responds to your data change because they are bound. There’s a lot more to this, do well to check out this codelab for hands-on experience, you can also check out this interesting project for simple DataBinding
implementation.
因此,布局基本上与ViewModel
的数据绑定在一起,更像观察者。 如果您发现该代码以及你看到的DataBinding
允许我们声明一个ViewModel
的布局,这一点,我们可以触发任何功能的ViewModel
,并直接就像你在37行看到,这就是的力量传播,我们认为该数据DataBinding
。 这也有助于消除您的活动/片段样板观察员,所以这又使我们回到回收站视图,没有更多的点击您的适配器的听众,你现在可以把它们放在你的版面项目,请注意您可以使用相同的ViewModel
作为主机片段/活动,因此您的布局会触发一个动作, ViewModel
会接收到该动作,并且布局会响应您的数据更改,因为它们已绑定。 除此之外,还有很多事情要做,可以很好地检查一下此代码实验室的动手经验,您也可以查看这个有趣的项目,以进行简单的DataBinding
实现。
Next up is Navigation Component.
接下来是导航组件。
Navigation Component
导航组件
Navigation Component is a library that eases navigation throughout your app. It preaches the use of a single activity and multiple fragments. Multiple fragments are connected together in a graph and hosted by a single activity. So you basically navigate from a fragment to another, and once set up its as easy as calling findNavController().navigate(fragment_id)
, it helps you manage backstack, and a lot more you can find on the official doc . Also do well to do this codelab to learn more.
导航组件是一个可简化整个应用程序导航的库。 它鼓吹使用单个活动和多个片段。 多个片段在图形中连接在一起,并由一个活动托管。 因此,您基本上可以从一个片段导航到另一个片段,并像调用findNavController().navigate(fragment_id)
一样轻松地对其进行设置,它可以帮助您管理后台程序,并且您可以在官方文档中找到更多内容。 也可以很好地完成此代码实验室以了解更多信息。
What next, Room database
接下来,房间数据库
Room is a library that is used for persistence in android, gone are the days when we used to use SQLiteOpenHelper
, we had to write boilerplate code and the awkward cursor.close()
after every query or transaction.
Room是一个用于在android中进行持久化的库,过去我们曾经使用SQLiteOpenHelper
的日子已经过去了,我们不得不在每次查询或交易后编写样板代码和笨拙的cursor.close()
。
Why should you use Room?
为什么要使用Room?
Room makes life easier when you want to support offline capability, you can’t for sure store everything in your shared preferences, so Room is good for storing complex data in a structured way. So, there are three components that makes up room which are Entity, Dao, and an abstract database class.
当您要支持脱机功能时,Room使生活更轻松,您不能确定是否将所有内容存储在共享的首选项中,因此,Room适合以结构化方式存储复杂数据。 因此,组成空间的三个组件分别是Entity,Dao和一个抽象数据库类。
An entity is a table with a data class structure so basically it is a model, for example, if you want to store user data you do something like this:
实体是具有数据类结构的表,因此从根本上说,它是一个模型,例如,如果要存储用户数据,请执行以下操作:
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "user")
data class User(
@PrimaryKey
val id: String,
val firstName: String,
val lastName: String
)
The second component is Dao: Data Access Object is an interface that is used to access the database, it allows you to fetch, create, update, and delete records. Generally anything that has to do with querying the database is done in the Dao class.
第二个组件是Dao:数据访问对象是用于访问数据库的接口,它使您可以获取,创建,更新和删除记录。 通常,与查询数据库有关的任何事情都在Dao类中完成。
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getUsers(): LiveData>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveUsers(user: User)
@Query("DELETE FROM user")
fun deleteAll()
}
Lastly, the abstract database class is where you set up the room database and connect all the daos and entities;
最后,在抽象数据库类中,您可以建立会议室数据库并连接所有dao和实体。
@Database(
entities = [User::class],
version = 1,
exportSchema = false
)
abstract class DatabaseClass : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: DatabaseClass? = null
fun getDatabase(context: Context): DatabaseClass {
if (instance == null) {
synchronized(DatabaseClass::class.java) {}
instance =
Room.databaseBuilder(context, DatabaseClass::class.java, "app.db")
.fallbackToDestructiveMigration()
.build()
}
return instance!!
}
}
}
There is more to room database, check out of the codelab, popular room medium by Muntenescu
房间数据库还有更多内容,请查阅代码实验室 ,由Muntenescu编写的受欢迎的房间介质
Next up is background work, coroutine and work manager
接下来是后台工作,协程和工作经理
Coroutine
协程
This will be very introductory. Firstly, as an Android developer you need to consider the performance of your app and how fast they respond, in this context, I’m talking about not running some heavy work on the main thread because the main thread is a single thread that handles all updates to the UI, like click listeners and all UI and lifecycle callbacks. The main thread can also dispatch other threads that may run in the background, therefore it is essential not to block the UI thread, blocking means the UI thread is waiting.
这将是非常介绍性的。 首先,作为一名Android开发人员,您需要考虑应用程序的性能以及响应速度,在这种情况下,我说的是不要在主线程上运行繁重的工作,因为主线程是处理所有内容的单个线程用户界面的更新,例如点击侦听器以及所有用户界面和生命周期回调。 主线程还可以分派可能在后台运行的其他线程,因此不要阻塞UI线程至关重要,阻塞意味着UI线程正在等待。
Coroutine is a lightweight thread that simplifies running asynchronous code in an idiomatic way, more like writing async code sequentially. It uses the suspend keyword to mark functions that may run in the background or main thread.
Coroutine是轻量级的线程,它以惯用的方式简化了异步代码的运行,更像是顺序编写异步代码。 它使用suspend关键字标记可能在后台或主线程中运行的功能。
To use coroutines we need: Job, Dispatcher and Scope
要使用协程,我们需要: Job,Dispatcher和Scope
Job is basically anything that can be canceled, all coroutine has a job and it can be used to cancel a coroutine.
作业基本上是可以取消的任何东西,所有协程都有一个作业,可以用来取消协程。
Dispatcher sends off coroutine to run on various threads, for example;
例如,分派器发出协程以在各种线程上运行;
Dispatchers.Main — run task on the main thread
Dispatchers.Main —在主线程上运行任务
Dispatchers.IO — run task off the main thread, like network calls
Dispatchers.IO-在主线程上运行任务,例如网络调用
Dispatchers.Default — run intensive work
Dispatchers.Default —运行大量工作
Scope combines information including a job dispatcher to define the context in which the coroutine runs. It keeps track of coroutine. So when a coroutine is launched, it runs within a scope
Scope组合了包括作业调度程序在内的信息,以定义协程运行所在的上下文。 它跟踪协程。 因此,当协程启动时,它将在一个范围内运行
Do well to lay your hands on this codelab to how it works, and read extensively about coroutine here by Sean McQuillan
很好地掌握此代码实验室的工作原理,并在此处广泛阅读Sean McQuillan撰写的有关协程的信息 。
WorkManager is a library that allows you to schedule tasks that are guaranteed to run even if the app exits or the device restart. Imagine you have to run a task in the background that is deferrable ( i.e the task doesn’t need to be run immediately) for example, you want to periodically upload some data from your room database to the server.
WorkManager是一个库,可让您安排即使应用程序退出或设备重新启动也能保证运行的任务。 假设您必须在可延迟的后台运行任务(例如,不需要立即运行该任务),例如,您想定期将一些数据从会议室数据库上载到服务器。
You can schedule tasks in two ways in WorkManager. One is OneTimeWorkRequest and the other is PeriodicWorkRequest.
您可以在WorkManager中以两种方式安排任务。 一个是OneTimeWorkRequest ,另一个是PeriodicWorkRequest 。
OneTimeWorkRequest is used to schedule a task that is to run once.
OneTimeWorkRequest用于计划要运行一次的任务。
PeriodicWorkRequest: as its name implies, run a task periodically, i.e frequently run a task within a given time interval. The minimum time interval is 15 minutes.
PeriodicWorkRequest:顾名思义,定期运行任务,即在给定的时间间隔内频繁运行任务。 最小时间间隔是15分钟。
When you create a WorkManager class, all you need to do is override doWork()
and return Result.Success
, you can also get the state of your work using observers. For example, you have an app where you always want to fetch data periodically and store them in your local DB or you want to send some data that has been stored in the local database to the server, you would typically register the WorkManager in your Application class to make it run once your app starts;
创建WorkManager类时,您只需重写doWork()
并返回Result.Success
,您还可以使用观察器获取工作状态。 例如,您有一个应用程序,在该应用程序中,您总是希望定期获取数据并将其存储在本地数据库中,或者要将一些已存储在本地数据库中的数据发送到服务器,通常需要在应用程序中注册WorkManager。使其在应用启动后立即运行的类;
The Application Class is where you could globally set up somethings and make it accessible throughout your app
在应用程序类中,您可以全局设置某些内容,并在整个应用程序中对其进行访问
Interesting things about WorkManager is that you can schedule a task to run based on certain conditions with Constraints
example are;
关于WorkManager的有趣之处在于,您可以使用Constraints
示例根据某些条件安排任务运行。
when there’s internet
getRequiredNetworkType()
有互联网时
getRequiredNetworkType()
when the battery is not low
requiresBatteryNotLow()
当电池电量不足时,
requiresBatteryNotLow()
when the device storage is not low
requiresStorageNotLow()
当设备存储空间不低时
requiresStorageNotLow()
when the device is charging
requiresCharging()
设备充电时
requiresCharging()
when the device is idle
requiresDeviceIdle()
设备闲置时
requiresDeviceIdle()
class ApplicationClass : Application() {
override fun onCreate() {
super.onCreate()
val constraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workManager = WorkManager.getInstance(applicationContext)
val request = PeriodicWorkRequest.Builder(WorkManagerClass::class.java,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MINUTES)
.setConstraints(constraint)
.build()
workManager.enqueue(request)
}
}
There’s a WorkManager codelab check it out to learn more.
有一个WorkManager的代码实验室进行检查以了解更多信息。
WorkManager isn’t always the way to do background work, check out this doc to know why and how.
WorkManager并非总是进行后台工作的方法,请查看此文档以了解原因和方式。
Material Components
材料成分
The new Material components (is it really new ), includes enhanced widgets with cool functionalities, beautiful designs. The material component is a new way to style your app, what you first need to do is add the dependency and then change the theme of your app with one of the material component themes, for example; Theme.MaterialComponents.Light.NoActionBar
and you good to go, among other things we have newly designed buttons, chips, EditText, and great attributes to customize them to your taste. You almost don’t have to design a round button using drawables, with the new MaterialComponent you customize it right in the XML.
新的Material组件(真的是新的)包括增强的小部件,这些小部件具有很酷的功能,精美的设计。 材料组件是一种样式化应用程序的新方法,例如,您首先需要添加依赖项,然后使用其中一个材料组件主题更改应用程序的主题。 Theme.MaterialComponents.Light.NoActionBar
和您一起去,除其他外,我们还新设计了按钮,芯片,EditText和出色的属性,可根据您的喜好自定义它们。 您几乎不必使用可绘制对象来设计圆形按钮,使用新的MaterialComponent即可在XML中自定义它。
Want to start using MDC, check this Nick’s medium post, also do well to visit the material component warehouse for more info
想要开始使用MDC,请查看此Nick的中篇文章,也可以很好地访问材料组件仓库以获取更多信息
Dependency Injection
依赖注入
DI in itself is a very broad topic, but let’s just have a basic understanding of what it is and why you need it.
DI本身是一个非常广泛的主题,但让我们对它的含义以及为什么需要它有一个基本的了解。
Now what is DI?
现在什么是DI?
DI is a concept whereby an object supplies the dependencies of another object. Hold still, let’s try to understand these terms well, for example a repository class needs a retrofit service to make requests from the server and probably also needs a dao object to fetch data from the database. The retrofit service and dao object are called dependencies and the act of providing them to the repository class is called dependency injection, just like a normal injection, the substance in the injection are the retrofit object/service and dao object that you want to inject into the body(repository class), and the injection itself is a class that houses the dependencies and provides it to whoever needs it :)
DI是一个概念,通过该概念,一个对象提供了另一个对象的依赖关系。 保持不变,让我们尝试很好地理解这些术语,例如,存储库类需要翻新服务才能从服务器发出请求,并且可能还需要dao对象才能从数据库中获取数据。 改造服务和dao对象称为依赖关系,将它们提供给存储库类的行为称为依赖关系注入 ,就像普通注入一样,注入中的物质是要注入的改造对象/服务和dao对象。 body(存储库类),注入本身是一个包含依赖项并将其提供给需要它的人的类:)
Now what birthed the idea of DI
现在诞生了DI的想法
let’s consider two classes A and B, consider this code below
让我们考虑两个类A和B,在下面考虑以下代码
class A {
val b = B()
b.doSomething()
}
class B {
fun doSomething
}
With the code above we can say A is dependent on B and B is a dependency of A. Something is wrong with this code, we’ll talk about that later. Let’s take another real-life scenario, imagine a computer system with a soldered RAM, i.e it’s the RAM is not replaceable nor removable, this means when the RAM is faulty, it cannot be replaced and cannot even be tested with another RAM to see if it’s really the RAM that is even faulty. Therefore, you have to get another system. This situation is due to a tightly coupled system, now let’s revisit the problem with the code above, B is created in A, there’s a general rule that says classes shouldn’t create or instantiate objects/classes themselves, instead, they should be provided, and how do you provide them, you do so through constructors. So in the above code, we will now pass our dependency(B) via the constructor of A;
通过上面的代码,我们可以说A依赖于B,而B是A的依赖项。此代码出了点问题,我们将在后面讨论。 让我们再来看一个真实的场景,想象一个带有焊接RAM的计算机系统,即RAM是不可更换或不可移动的,这意味着当RAM出现故障时,它不能被更换,甚至不能用另一个RAM进行测试以查看是否确实是RAM甚至出现故障。 因此,您必须获得另一个系统。 这种情况是由于系统紧密耦合造成的,现在让我们回顾一下上面的代码的问题,B是在A中创建的,有一条通用规则规定类不应自行创建或实例化对象/类,而应提供它们,以及如何提供它们,您可以通过构造函数来实现。 因此,在上面的代码中,我们现在将通过A的构造函数传递dependency(B);
class A(private val b: B) {
b.doSomething()
}
class B {
fun doSomething
}
Why dependency injection? It’s a good software practice and the most benefit of all is it makes testing a breeze.
为什么要进行依赖注入? 这是一个很好的软件实践,最大的好处是它使测试变得轻而易举。
We can manually create and provide dependencies through constructors which is good, but as the app grows it becomes a pain in the ass and you should consider delegating it to a library called Dagger. There are other libraries like kodein and koin
我们可以通过构造函数手动创建并提供依赖关系,这很好,但是随着应用程序的增长,它变得很痛苦,您应该考虑将其委派给名为Dagger的库。 还有其他库,例如kodein和koin
Check out the official doc on Dagger and do well to lay your hands on this Dagger codelab. Ideally you should learn more about testing before moving into Dagger, I’d recommend this testing codelab
查看有关Dagger的官方文档 ,并熟练掌握此Dagger代码实验室 。 理想情况下,在进入Dagger之前,您应该了解有关测试的更多信息,我建议使用此测试代码实验室
And that is all for now, I hope this helped someone
到此为止,我希望这对某人有所帮助
There are a number of things to look out for in Android Development that hasn’t be covered by this article or at least its been introduced, still, the best place to find them is the official doc;
Android开发中有很多需要注意的地方,本文没有涉及,或者至少没有介绍过,但是,找到它们的最佳位置是官方文档。
Catch me on twitter anytime
随时在Twitter上吸引我
翻译自: https://medium.com/swlh/modern-android-development-3013f3034610