- Android Contacts fetching using Kotlin Coroutines
- From Kotlin synthetics to Android ViewBinding: the definitive guide
- Binding your views like a pro in 2 minutes.
- Setup
- Enable view binding
- Remove android extensions plugin
- Parcelize annotations
- Activities
- Fragments
- ViewHolder
- Includes
- With a view group
- With a
- Conclusion
- Современная Android разработка на Kotlin. Часть 1
- Каким будет наше приложение?
- Android Studio
- Kotlin
- 2. Build Variants
- 3. ConstraintLayout
- 4. Библиотека привязки данных Data Binding
- Как ButterKnife помогает нам?
- Что плохо в ButterKnife?
- Что насчёт библиотеки привязки данных?
- Getter’ы и Setter’ы в Котлине
Android Contacts fetching using Kotlin Coroutines
Apr 20, 2020 · 4 min read
In this article I’m going to talk about how you can fetch 1000’s of contacts quickly and efficiently using Coroutines in kotlin. Many Android developers face this problem and it takes usually 40 seconds or more to fetch approximately 700 contacts or more, and i searched across many Android tutorials to get efficient and optimised way of fetching contacts quickly at the same time leveraging the power of Coroutines.
When you Google on how to fetch contacts, many of you would come across a solution like this:
The above sol u tion takes approximately half a minute or more to fetch all the contacts and in the mean time users keep waiting, which leads to a poor UX (User Experience).
We are going to solve the above problem using Coroutines and fetch all contacts details approximately within a second.
But first let’s understand the how Contacts Provider stores the data internally and which tables does it use?
A legend once said that picture is worth a thousand words.
Let’s check out the Database Schema of what Contacts Provider uses internally to store data.
_ID field in every table is a primary key which auto increments on every insertion.
CONTACT_ID field in RawContacts and Data table are foreign key which points to primary key of Contacts table.
RAW_CONTACT_ID field in Data table is foreign key which points to Primary key of RawContacts table.
User cannot insert row in Contacts table, as it will give UnsupportedOperationException if tried to add data using insert(). Programmers can add user details in RawContacts and it’s associated data in Data table. Contacts provider adds single row to Contacts table using Aggregation of data in RawContacts table. Data table stores all the actual data of a user like phone number, email ID, address, etc….
Let’s say we want to add 3 contact details of a person with the same name (2 phone numbers and 1 email ID), the Contacts provider inserts a 1 new row in Contacts table which represents a person, and 1–3 rows in RawContacts table (depends on query and data) and 3 rows in Data table which holds the actual value of contact (e.g. phone number, email, address, etc..).
When we refer Phone number in code like ContactsContract.CommonDataKinds.Phone.NUMBER or email address like ContactsContract.CommonDataKinds.Email. ADDRESS both points to same column DATA1 column. In Data table, 2 phone numbers and 1 email ID are stored in same DATA1 coloumn as in above case with different MIME type in MIMETYPE column. Similarly, Contacts Provider uses same Data table for storing different types of data in columns from DATA1 to DATA15 columns depending on types of data stored.
Since we covered the basic understanding of how Contacts Provider works lets analyse why the above code works slow and how we can optimise it with logic changes also using kotlin Coroutines.
Both read and write operation to Contacts Provider are time consuming execution, so we should minimise the number of operation to Contacts Provider. In the above gist we first fetch all the Contacts, and then for each Contact we fetch it’s associated number and email. So if your phone has 1000 contacts and each contact has multiple numbers that means we are performing 1001 read operations (1 initial Contacts read operation fetching all contacts + 1000 Read operation for fetching multiple numbers of a contact in each operation) using Contacts Provider. If each read operation takes 50 milliseconds, then complete fetching of contact takes approximately 50 seconds.
We will simplify the above solution with just 3 read operations from Contacts Provider, which should take 150 milliseconds, since these 3 executions are independent using kotlin concurrent using async we can perform all 3 operations in parallel and bring down the fetch operation to just 50 milliseconds.
We’ll perform 3 read operations as follows:
- Fetch all Contacts and add it to ArrayList.
- Fetch all Contact Numbers from Data table and store it in HashMap with CONTACT_ID as key.
- Fetch all Email IDs from Data table and store it in HashMap with CONTACT_ID as key.
Finally after fetching all the details, we’ll iterate through all Contacts in ArrayList and get it’s associated numbers and emails from HashMap.
Источник
From Kotlin synthetics to Android ViewBinding: the definitive guide
Binding your views like a pro in 2 minutes.
Since Kotlin 1.4.20, JetBrains deprecated the kotlin-android-extensions plugin in favor of Android View Binding solution.
If you have been using this plugin, it’s time to move on before this feature is completely removed (or switch to Compose?).
For having completely refactored our Android application to use View Binding, we know that it can be somehow painful and sometimes it looks less simple with this new solution. For example, the binding of your view will have to be done differently if you’re working with an Activity or a Fragment. If your layout uses include there are some tricky ways to handle them too.
That’s why we’ve been implementing some convenient methods to make it simpler.
Setup
If you’re refactoring your whole application code, we advise you to do it module per module to avoid committing too many updates.
Enable view binding
To enable the ViewBinding feature, you just need to enable this build feature on your module build.gradle files:
Remove android extensions plugin
If you’ve been using kotlin-android-extensions before, remove the plugin from the build.gradle file too:
Don’t forget to also remove the Android Extensions experimental flag:
Parcelize annotations
The Parcelize annotations have been moved to another package & the previous one has been deprecated.
So, if you used the Kotlin Parcelize annotation to automatize the implementation of your Parcelable objects, you’ll need to use the new plugin :
Then, replace imports in your code from kotlinx.android.parcel to kotlinx.parcelize.
Activities
To set up an Activity to use your binding class you basically have to:
1- Declare a late init variable for your binding class.
2- In the onCreate method, inflate your layout using the static inflate method from the generated Binding class.
3- Call the Activity setContentView with the root view from your binding class.
We can definitely make it way shorter using some extensions:
Our viewBinding method is a simple delegate that inflates lazily the view of the activity by using the inflater method given as a parameter.
In the Activity we just need to declare the binding as a simple val property and set the content view of the activity.
Fragments
Using View Binding in Fragments is quite less easy as you have to do many things to make it works:
- 1- Declare a var property for your binding class instance.
- In the onCreateView callback, inflate the layout for your binding class & return the root view of it.
- To avoid potential leaks, reset your binding class to null in the onDestroyView callback.
As per the official documentation, it should look like that:
That’s a lot of code to add & you should also pay attention to potential leaks on the binding class. This can also be disturbing as the method is quite different from using it in an Activity. Note that you also need to use the !! operator to hide the nullability of the original field.
There’s a lot of room for improvement here and I found a way better solution to deal with it in 1 line:
Yep, really simple!🚀
Here are some explanations:
- We pass to the Fragment constructor the id of our fragment layout, which will be inflated for us.
- We declare the binding property that will handle the fragment lifecycle work for us.
Have a look at this Gist:
Our viewBindingWithBinder simply instantiate a delegate object that will bind the view of our fragment lazily. As the Fragment already inflate the view from the layout id that we pass to it in its constructor, we just need to bind our binding class from it.
The FragmentAutoClearedValueBinding class is the one that will be responsible to clear the reference to the binding class when the fragment gets destroyed by adding an observer to its lifecycle. In the getValue method, we bind the view to our binding class the first time we access our binding property from our fragment.
ViewHolder
If you’ve been using Kotlin synthetics, you probably have something that looks like this:
You can now remove the usage of LayoutContainer and make some updates in your view holder:
Here we have simply changed our constructor to take the binding class in parameter instead of having a view object.
To instantiate your view holder, you can create a static method in a companion object:
Includes
In your layouts, you probably have views that are included in your layout using the include tag. Then, you must be aware that it can be tricky to deal with it with view binding.
With a view group
If your included view has a root view that is a ViewGroup (LinearLayout, FrameLayout, etc.) then you can set an id to the include tag in your main layout.
The generated LayoutBinding class will have a property mergeLayout of type LayoutMergeBinding that reference the layout_mege layout views.
So in an activity, you can easily reference your TextView as:
With a
If your included view uses a tag in it like in this example:
And your main layout is:
Then, the layout_merge.xml file content will be merged into your main layout. But, the binding class will not expose a property titleLbl (it will only contain your root LinearLayout ) 😭.
So, what can you do to access to your titleLbl ? You’ll need to re-bind your view with another binding class. As each layout files have its own binding class, your layout_merge layout will have a LayoutMergeBinding class generated. That’s the one we will use to retrieve our views.
From an activity, you can add another property for the 2nd binding class:
The mergeBinding property will then allow you to access the titleLbl view.
Conclusion
We hope this article was useful to you if you’re still refactoring your views to use ViewBinding or to give you some hints to refactor how you’re using it.
And you, how did you implement it into your app? Don’t hesitate to share with us your tip&tricks!
If you want to join our Bureau of Technology or any other Back Market department, take a look here, we’re hiring! 🦄
Источник
Современная Android разработка на Kotlin. Часть 1
Данная статья является перевом статьи от Mladen Rakonjac
Очень сложно найти один проект, который охватывал бы всё новое в разработке под Android в Android Studio 3.0, поэтому я решил написать его. В этой статье мы разберём следующее:
- Android Studio 3
- Язык программирования Kotlin
- Варианты сборки
- ConstraintLayout
- Библиотека привязки данных Data Binding
- Архитектура MVVM + паттерн repository (с mapper’ами) + Android Manager Wrappers
- RxJava2 и как это помогает нам в архитектуре
- Dagger 2.11, что такое внедрение зависимости, почему вы должны использовать это.
- Retrofit (Rx Java2)
- Room (Rx Java2)
Каким будет наше приложение?
Наше приложение будет самым простым, которое охватывает все перечисленные выше вещи: у него будет только одна функция, которая извлекает все репозитории пользователя googlesamples из GitHub, сохраняет эти данные в локальной базе данных и показывает их пользователю.
Я попытаюсь объяснить как можно больше строк кода. Вы всегда можете посмотреть код, который я опубликовал на GitHub.
Android Studio
Чтобы установить Android Studio 3, перейдите на эту страницу
Android Studio 3 поддерживает Kotlin. Откройте Create Android Project. Там вы увидите новый флажок с меткой Include Kotlin support. Он выбран по умолчанию. Дважды нажмите кнопку Далее и выберите Empty Activity, затем нажмите Finish.
Поздравляю! Вы сделали первое приложение для Android на Котлине 🙂
Kotlin
Вы можете видеть MainActivity.kt:
Расширение .kt означает, что файл является файлом Kotlin.
MainActivity: AppCompatActivity() означает, что мы расширяем AppCompatActivity.
Кроме того, все методы должны иметь ключевое слово fun и в Котлине вам не нужно использовать ;, но вы можете, если хотите. Вы должны использовать ключевое слово override, а не аннотацию, как в Java.
Так что же означает ? в savedInstanceState: Bundle?? Это означает, что savedInstanceState может быть типа Bundle или типа null. Kotlin null безопасный язык. Если у вас есть:
вы получите ошибку компиляции, потому что a должна быть инициализированна и это не может быть null. Это означает, что вы должны написать:
Кроме того, вы получите ошибку компиляции, если вы это сделаете:
Чтобы сделать a nullable, вы должны написать:
Почему эта важная особенность языка Котлина? Это помогает нам избежать NPE. Разработчики Android уже устали от NPE. Даже создатель null, сэр Тони Хоар, извинился за изобретение. Предположим, что мы имеем nullable nameTextView. Если переменная равна null, то в следующем коде мы получим NPE:
Но Котлин, на самом деле, хорош, он не позволят нам делать даже такое. Он заставляет нас использовать оператор ? или оператор !!. Если мы используем оператор ?:
Строка будет исполнена только если nameTextView не null. В ином случае, если вы используете оператор !!:
Мы получим NPE если nameTextView null. Это для авантюристов :).
Это было небольшое введение в Kotlin. Когда мы продолжим, я остановлюсь, чтобы описать другой специфический код на Котлине.
2. Build Variants
В разработке часто вы имеете различные окружения. Наиболее стандартным является тестовое и производственное окружение. Эти среды могут отличаться в URL-адресах сервера, иконке, имени, целевом API и т.д. На fleka в каждом проекте у вас есть:
- finalProduction, который отправляется в Google Play Store.
- demoProduction, то есть версия с URL-адресом production сервера с новыми функциями, которые всё ещё не находятся в Google Play Store. Наши клиенты могут установить эту версию рядом с Google Play, чтобы они могли протестировать ее и дать нам обратную связь.
- demoTesting, то же самое, что и demoProduction с тестовым URL-адресом сервера.
- mock, полезен для меня как для разработчика и дизайнера. Иногда у нас есть готовый дизайн, и наш API ещё не готов. Ожидание API, чтобы быть начать разработку — не решение. Этот вариант сборки снабжён поддельными данными, поэтому команда дизайнеров может проверить его и дать нам обратную связь. Очень полезно это не откладывать. Когда API уже готов, мы перемещаем нашу разработку в окружение demoTesting.
В этом приложении мы будем использовать всех их. У них будут отличаться applicationId и имена. В gradle 3.0.0 есть новый API flavorDimension, который позволяет смешивать разновидности продукта, так, например, вы можете смешать разновидности demo и minApi23. В нашем приложении мы будем использовать только «default» flavorDimension. Перейдите в build.gradle для приложения и вставьте этот код внутри android <>
Перейдите в strings.xml и удалите строку app_name, чтобы у нас не было конфликтов. Затем нажмите Sync Now. Если вы перейдете в Build Variants, расположенным слева от экрана, вы увидите 4 варианта сборки, каждый из которых имеет два типа сборки: Debug и Release. Перейдите к варианту сборки demoProduction и запустите его. Затем переключитесь на другой и запустите его. Вы должны увидеть два приложения с разными именами.
3. ConstraintLayout
Если вы откроете activity_main.xml, вы увидите, что этот layout — ConstrainLayout. Если вы когда-либо писали приложение под iOS, вы знаете об AutoLayout. ConstraintLayout действительно похож на него. Они даже используют один и тот же алгоритм Cassowary.
Constraint помогает нам описать связи между View. Для каждого View у вас должно быть 4 Constraint, один для каждой стороны. В данном случае наш View ограничен родителем с каждой стороны.
Если вы передвинете TextView «Hello World» немного вверх во вкладке Design, во вкладке Text появится новая линия:
Вкладки Design и Text синхронизируются. Наши изменения во вкладке Design влияют на xml во вкладке Text и наоборот. Vertical_bias описывает вертикальную тенденцию view его Constraint. Если вы хотите центровать вертикально, используйте:
Давайте сделаем чтобы наш Activity показал только один репозиторий. В нём будут имя репозитория, количество звезд, владелец, и он будет показывать, есть ли у репозитория issues, или нет.
Чтобы получить такой layout, xml должен выглядеть так:
Пусть tools:text вас не смущает. Он просто помогает нам видеть хороший предварительный просмотр макета (layout’а).
Вы можете заметить, что наш макет плоский, ровный. Вложенных макетов нет. Вы должны использовать вложенные макеты как можно реже, поскольку это может повлиять на производительность. Более подробную информацию об этом вы можете найти здесь. Кроме того, ConstraintLayout отлично работает с разными размерами экрана:
и мне кажется, что я могу добиться желаемого результата очень быстро.
Это было небольшое введение в ConstraintLayout. Вы можете найти Google code lab здесь, и документацию о ConstraintLayout на GitHub.
4. Библиотека привязки данных Data Binding
Когда я услышал о библиотеке привязки данных, первое вопрос, который я задал себе: «ButterKnife работает очень хорошо для меня. Кроме того, я использую плагин, который помогает мне получать View из xml. Зачем мне это менять?». Как только я узнал больше о привязке данных, у меня было такое же чувство, какое у меня было, когда я впервые использовал ButterKnife.
Как ButterKnife помогает нам?
ButterKnife помогает нам избавиться от скучного findViewById. Итак, если у вас 5 View, без Butterknife у вас есть 5 + 5 строк, чтобы привязать ваши View. С ButterKnife у вас есть 5 строк. Вот и всё.
Что плохо в ButterKnife?
ButterKnife по-прежнему не решает проблему поддержки кода. Когда я использовал ButterKnife, я часто получал исключение во время выполнения, потому что я удалял View в xml, и не удалял код привязки в классе Activity / Fragment. Кроме того, если вы хотите добавить View в xml, вам нужно снова сделать привязку. Это очень скучно. Вы теряете время на поддерживание связей.
Что насчёт библиотеки привязки данных?
Есть много преимуществ! С помощью библиотеки привязки данных вы можете привязать свои View всего одной строкой кода! Позвольте мне показать вам, как это работает. Давайте добавим библиотеку Data Binding в наш проект:
Обратите внимание, что версия компилятора Data Binding должна совпадать с версией gradle в файле build.gradle проекта:
Нажмите Sync Now. Перейдите в activity_main.xml и оберните ConstraintLayout тегом layout:
Обратите внимание, что вам нужно переместить все xmlns в тег layout. Затем нажмите иконку Build или используйте сочетание клавиш Ctrl + F9 (Cmd + F9 на Mac). Нам нужно собрать проект, чтобы библиотека Data Binding могла сгенерировать класс ActivityMainBinding, который мы будем использовать в нашем классе MainActivity.
Если вы не выполните сборку проекта, вы не увидите класс ActivityMainBinding, потому что он генерируется во время компиляции. Мы все еще не закончили связывание, мы просто сказали, что у нас есть ненулевая переменная типа ActivityMainBinding. Кроме того, как вы можете заметить, я не указал ? в конце типа ActivityMainBinding, и я не инициализировал его. Как это возможно? Модификатор lateinit позволяет нам иметь ненулевые переменные, ожидающие инициализации. Подобно ButterKnife, инициализация привязки должна выполняться в методе onCreate, когда ваш Activity будет готов. Кроме того, вы не должны объявлять привязку в методе onCreate, потому что вы, вероятно, используете его вне области видимости метода onCreate. Наша привязка не должна быть нулевой, поэтому мы используем lateinit. Используя модификатор lateinit, нам не нужно проверять привязку переменной каждый раз, когда мы обращаемся к ней.
Давайте инициализируем нашу переменную binding. Вы должны заменить:
Вот и всё! Вы успешно привязали свои View. Теперь вы можете получить к ним доступ и применить изменения. Например, давайте изменим имя репозитория на «Modern Android Habrahabr Article»:
Как вы можете видеть, мы можем получить доступ ко всем View (у которых есть id, конечно) из activity_main.xml через переменную binding. Вот почему Data Binding лучше, чем ButterKnife.
Getter’ы и Setter’ы в Котлине
Возможно, вы уже заметили, что у нас нет метода .setText (), как в Java. Я хотел бы остановиться здесь, чтобы объяснить, как геттеры и сеттеры работают в Kotlin по сравнению с Java.
Во-первых, вы должны знать, почему мы используем сеттеры и геттеры. Мы используем их, чтобы скрыть переменные класса и разрешить доступ только с помощью методов, чтобы мы могли скрыть элементы класса от клиентов класса и запретить тем же клиентам напрямую изменять наш класс. Предположим, что у нас есть класс Square в Java:
Используя метод setA (), мы запрещаем клиентам класса устанавливать отрицательное значение стороне квадрата, оно не должно быть отрицательным. Используя этот подход, мы должны сделать a приватным, поэтому его нельзя установить напрямую. Это также означает, что клиент нашего класса не может получить a напрямую, поэтому мы должны предоставить getter. Этот getter возвращает a. Если у вас есть 10 переменных с аналогичными требованиями, вам необходимо предоставить 10 геттеров. Написание таких строк — это скучная вещь, в которой мы обычно не используем наш разум.
Kotlin облегчает жизнь нашего разработчика. Если вы вызываете
это не означает, что вы получаете доступ к a непосредственно. Это то же самое, что
в Java. Причина заключается в том, что Kotlin автоматически генерирует геттеры и сеттеры по умолчанию. В Котлине, вы должны указать специальный сеттер или геттер, только если он у вас есть. В противном случае, Kotlin автогенерирует его для вас:
field? Что это? Чтобы было ясно, давайте посмотрим на этот код:
Это означает, что вы вызываете метод set внутри метода set, потому что нет прямого доступа к свойству в мире Kotlin. Это создаст бесконечную рекурсию. Когда вы вызываете a = что-то, он автоматически вызывает метод set.
Надеюсь, теперь понятно, почему вы должны использовать ключевое слово field и как работают сеттеры и геттеры.
Вернемся к нашему коду. Я хотел бы показать вам ещё одну замечательную особенность языка Kotlin, apply:
apply позволяет вам вызывать несколько методов на одном экземпляре.
Мы все еще не закончили привязку данных, есть ещё много дел. Давайте создадим класс модели пользовательского интерфейса для репозитория (этот класс модели пользовательского интерфейса для репозитория GitHub хранит данные, которые должны отображаться, не путайте их с паттерном Repository). Чтобы сделать класс Kotlin, вы должны перейти в New -> Kotlin File / Class:
В Kotlin первичный конструктор является частью заголовка класса. Если вы не хотите предоставлять второй конструктор, это всё! Ваша работа по созданию класса завершена здесь. Нет параметров конструктора для назначений полей, нет геттеров и сеттеров. Целый класс в одной строке!
Вернитесь в класс MainActivity.kt и создайте экземпляр класса Repository:
Как вы можете заметить, для построения объекта не нужно ключевого слова new.
Теперь перейдем к activity_main.xml и добавим тег data:
Мы можем получить доступ к переменной repository, которая является типом Repository в нашем макете. Например, мы можем сделать следующее в TextView с идентификатором repository_name:
В TextView repository_name будет отображаться текст, полученный из свойства repositoryName переменной repository. Остается только связать переменную репозитория от xml до repository из MainActivity.kt.
Нажмите Build, чтобы сгенерировать библиотеку привязки данных для создания необходимых классов, вернитесь в MainActivity и добавить две строки:
Если вы запустите приложение, вы увидите, что в TextView появится «Habrahabr Android Repository Article». Хорошая функция, да? 🙂
Но что произойдёт, если мы сделаем следующее:
Отобразится ли новый текст через 2 секунды? Нет, не отобразится. Вы должны заново установить значение repository. Что-то вроде этого будет работать:
Но это скучно, если нужно будет делать это каждый раз, когда мы меняем какое-то свойство. Существует лучшее решение, называемое Property Observer.
Давайте сначала опишем, что такое паттерн Observer, нам понадобится это в разделе rxJava:
Возможно, вы уже слышали об androidweekly.net. Это еженедельный информационный бюллетень об Android разработке. Если вы хотите его получить, вам необходимо подписаться на него, указав свой адрес электронной почты. Позже, если вы захотите, вы можете остановить отказаться от подписки на своем сайте.
Это один из примеров паттерна Observer / Observable. В данном случае, Android Weekly — наблюдаемый (Observable), он выпускает информационные бюллетени каждую неделю. Читатели — это наблюдатели (Observers), они подписываются на него, ждут новых выпусков, и, как только они получают её, они читают её, и если некоторые из них решат, что им это не нравится, он / она может прекратить следить.
Property Observer, в нашем случае, представляет собой XML-макет, который будет прослушивать изменения в экземпляре Repository. Таким образом, Repository является наблюдаемым. Например, как только свойство name класса Repository изменяется в экземпляре класса, xml должен обновится без вызова:
Как сделать это с помощью библиотеки привязки данных? Библиотека привязки данных предоставляет нам класс BaseObservable, который должен быть реализован в классе Repository:
BR — это класс, который автоматически генерируется один раз, когда используется аннотация Bindable. Как вы можете видеть, как только новое значение установлено, мы узнаём об этом. Теперь вы можете запустить приложение, и вы увидите, что имя репозитория будет изменено через 2 секунды без повторного вызова функции executePendingBindings ().
Для этой части это всё. В следующей части я напишу о паттерне MVVM, паттерне Repository и об Android Wrapper Managers. Вы можете найти весь код здесь. Эта статья охватывает код до этого коммита.
Источник