- Build an Android Chat app with Jetpack Compose
- Intro and context
- Project setup
- Channel List Screen
- Setting up navigation
- Message List Screen
- Message Input
- Conclusion
- Первое впечатление от Android Jetpack Compose
- Что такое Android Jetpack Compose?
- 1. Независимость от релизов Android
- 2. Весь API на Kotlin
- 3. Composable = Композитный: использование композиции вместо наследования
- 4. Однонаправленный поток данных
- 5. Улучшение отладки
- Хватит слов, покажите код
- 1. FrameLayout vs Wrap + Padding + Background
- 2. Вертикальный LinearLayout vs Column
- 2a. Отступы
- 2b. Интервалы
- 2c. Горизонтальный LinearLayout vs Row
- 2d. Gravity vs Alignment
- 2d. Замечание
- 3.Стили и темы
- Что посмотреть/почитать
- Выводы
Build an Android Chat app with Jetpack Compose
Table of Contents
Intro and context
Channel List Screen
Setting up navigation
Message List Screen
Sign up for a free Maker account
Jetpack Compose: First Impressions and Learning Resources
Android Chat SDK on GitHub
Android Chat Tutorial
Wanna build a functional chat app with Jetpack Compose? This is the tutorial for you!
• 9 months ago
Stream now provides a fully-featured Jetpack Compose Chat SDK that you can use instead of the basic approach described in this article. Check out the Compose Chat Messaging Tutorial and give it a try today!
Intro and context
In our previous article about Jetpack Compose, we shared our initial impressions and recommended some learning resources. This time, we’ll take Compose out for a spin by building a basic chat UI implementation with it!
Of course, you might be wondering why we’re building a Compose Chat app when there’s an official Google sample, Jetchat, which is also a chat app using Compose. That sample is a basic UI demo with a small bit of hardcoded data. Here, we’re building a real, functional chat app instead that’s hooked up to a real-time backend service.
We already ship a UI Components library in our Chat SDK which contains ready-to-go, fully-featured Android Views to drop into your app, along with ViewModels that let you connect them to business logic in just a single line of code. You can check out how that works in our Android Chat Tutorial.
This time, we’re going to reuse the ViewModels (and a View) from that implementation, and build our Compose-based UI on top of that. This won’t be an ideal scenario, as those ViewModels are designed to work with the Views that they ship with.
Having dedicated support for Compose in the SDK would make this much nicer and simpler, and that’s something we’ll be working on in the future. However, as you’ll see, it’s already quite easy to integrate Stream Chat with Jetpack Compose, without any specific support for it!
The project we’re building in this article is available on GitHub, feel free to clone the project and play around with it on your own! We recommend getting your own Stream API key for this, which you can do by signing up for a free trial. Stream is free for hobby projects and small companies even beyond the trial — just apply for a free Maker account.
Project setup
To use Jetpack Compose, we’ll create our project in the latest Canary version of Android Studio. This contains a project template for Compose apps.
After creating the project, our first step will be to add the Stream UI Components SDK as a dependency, as well as some Jetpack Compose dependencies we need. Some of our dependencies come from Jitpack, so make sure to add that repository in the settings.gradle file:
Then, in the module’s build.gradle file, replace the dependencies block with the following:
Check out the GitHub project if you get tangled up in the dependencies.
Next, we’ll set up the Stream Chat SDK, providing an API key and user details to it. This is normally done in your custom Application class. To make things simple, we’ll use the same environment and user as our tutorial.
We’re initializing all three layers of our SDK here: the low-level client, the domain providing offline support, and the UI components. For more info about these, take a look at the documentation. Finally, we’re connecting our user, using a hardcoded, non-expiring example token from the tutorial.
Since we created a custom Application class, we have to update our Manifest to use this ChatApplication when the app is launched. We’ll also set windowSoftInputMode on the MainActivity so that we’ll have better keyboard behaviour later on.
Channel List Screen
First, we’ll create a screen that lists the channels available to our user. The UI Components library contains a ChannelListView component for this purpose, and we can reuse its ViewModel here.
Let’s see what this code does step-by-step:
- We use the viewModel() method provided by Compose to get a ViewModel for our current context. This allows us to specify a factory that creates a ViewModel, which we need in the case of ChannelListViewModel to pass in some parameters to it. We’ll stick to the default parameters of the SDK’s ChannelListViewModelFactory for now, which will filter for channels that our current user is a member of.
- We observe the LiveData state in the ViewModel as a composable State , and we copy it to a local variable so that smart casts will work on it correctly. We also skip rendering anything if this State happens to be null .
- We’ll have the ChannelListScreen fill the entire screen, and center any content inside it.
- If the state is loading, we’ll display a basic progress indicator.
- Otherwise, we’ll display the list of channels in a LazyColumn . Each item will be a ChannelListItem responsible for showing info for a single channel. We’re also adding a simple Divider after each item.
Now, let’s see how we can render a single ChannelListItem in the list:
- We’re rendering each item as a Row .
- Inside that, we’re adding an Avatar representing the Channel at the start.
- Then we add a Column , laying out two pieces of Text vertically. These will contain the channel’s title and a preview of the latest message. Note the LocalContext.current API being used to grab a Context in Compose, as well as the TextOverflow.Ellipsis option that will make long messages behave nicely in the preview.
We have one last Composable to implement, the Avatar used above. Our UI components SDK ships an AvatarView with complex rendering logic inside it, which we don’t want to reimplement for now. We can use Jetpack Compose’s interop features to include our regular Android View inside Compose UI.
This will be really simple, just a few lines of code to create a wrapper:
The AndroidView function allows us to create. Well, an Android View , which we get a Context for. It also gives us a way to hook into Compose’s state updates using its update parameter, which will be executed whenever composable state (in our case, the Channel object) changes. When that happens, we set the new Channel on our AvatarView .
Finally, we can add the ChannelListScreen to MainActivity :
Building and running this code gives us a working list of channels loaded from Stream’s backend, with performant scrolling thanks to LazyColumn . Not bad for a hundred lines of code for the entire thing!
Setting up navigation
Continuing with our chat app, let’s create a new screen, where we’ll display the list of messages in a channel. For this, we’ll need to set up click listeners and navigation. We’ll use the Navigation Component for Jetpack Compose here.
Step one, we’ll make our ChannelListItem clickable, and add an onClick parameter that it will call when it was clicked:
ChannelListScreen will take a NavController as its parameter, and use it to navigate to a new destination when an item was clicked, based on the cid of the channel.
We’ll update MainActivity by moving our top-level Composable code to a ChatApp composable, and setting up navigation in there.
- We’re creating a NavController and a NavHost for our application.
- Our first and initial destination is a composable: ChannelListScreen . We pass the nav controller to it.
- Our second destination will be a new composable called MessageListScreen . Here, we pass in both the nav controller and the argument that was used to navigate to it. We can grab this from the NavBackStackEntry parameter.
Message List Screen
Our message list will be a LazyColumn similar to the channel list we created before. For displaying messages, the UI Components library gives us a MessageListViewModelFactory which can create a few different ViewModel instances. This takes the cid of the channel we want to display.
We’ll implement the MessageList like this:
- We get a MessageListViewModel from the factory, which will fetch messages for us, and expose it as LiveData .
- As before, we convert the LiveData state from the ViewModel into composable State .
- We have three states to handle, defined by a sealed class inside MessageListViewModel . The first state is a loading state, the second is a state that pushes us to the previous screen (using the nav controller), and the third is the result state, where we have a list of messages.
- In the result state, we’ll grab the list of messages received, filter for only MessageItem s (excluding things such as date separators that we don’t want to render for now). We also filter for messages that have non-blank text — for example, some messages might have only images attached to them, so we’ll skip those for simplicity. Finally, we reverse the list, to match what we’re doing in the next step.
- We use reverseLayout on LazyColumn to stack its items from the bottom. This is similar to using stackFromEnd on a RecyclerView .
- Each item will be rendered by a MessageCard composable.
For a single message, we’ll use the following layout:
This code is mostly straightforward, but let’s review some of its important bits:
- We take a MessageItem as a parameter.
- Depending on whether this is our current user’s message, or someone else’s, we align it to one of the sides of the screen. We also set colours based on this in a few places.
- We use a cardShapeFor helper method to create the shape of the Card that holds the message. This will create a shape with rounded corners, except for one of the bottom corners, giving us a chat bubble look.
- We display the username under each message, so that we can distinguish between messages sent by others.
At this point, we can build and run again, and clicking a channel will navigate to the new screen, displaying the list of messages in it.
Message Input
For our final piece of Jetpack Compose chat implementation, we’ll add an input view on the message list screen so that we can send new messages.
First, we’ll modify MessageListScreen and place a new composable under MessageList :
The weight modifier on MessageList will make it take up the maximum available space above the new MessageInput . We pass in the factory to MessageInput as well, so that it’s able to access the currently open channel’s data via ViewModels.
- This time, we’ll use a MessageInputViewModel , which normally belongs to the MessageInputView shipped in the UI components library, and handles input actions.
- We create a piece of composable state to hold the current input value.
- This local helper function calls into the ViewModel and sends the message to the server. Then, it clears the input field by resetting the state.
- We use a TextField to capture user input. This displays the current inputValue , and modifies it based on keyboard input. We also set up IME options so that our software keyboard displays a Send button, and we handle that being tapped by calling sendMessage .
- We’re also adding a Button that the user can tap to send a message. This is enabled/disabled dynamically based on the current input value.
- The button will show an icon from the default Material icon set. Not to skimp on accessibility, we also add a content description string for the send icon. We grab this from regular Android resources using stringResource , which allows us to localize it as usual. Make sure to create this resource in your project.
Let’s build and run for the last time, and we have a working chat app now! We can browse channels, open them, read messages, and send new messages.
Conclusion
This complete implementation, including UI, logic, and a connection to a real server is roughly 250 lines of code in total. Almost all of this is UI code with Jetpack Compose, the integration with Stream Chat’s ViewModels and Views is just a small fraction of it.
Follow us on Twitter @getstream_io, and the author @zsmb13 for more content like this. If you liked this tutorial, tweet at us and let us know!
As a reminder, you can check out the full project and play with it on GitHub. We recommend expanding this project to learn more about Compose — a good first goal could be to add a login screen, so that you can have different users on different devices.
To jump into that, sign up for a free trial of Stream Chat. If you’re doing this for a side project or you’re a small business, you can use our SDK for with a free Maker account, even beyond the trial period!
Here are some more useful links to continue exploring:
- The Stream Chat Android SDK on GitHub, including the UI Components Sample app that showcases many features, and includes a login implementation you can take ideas from
- Our regular, non-Compose Android Tutorial
- The Stream Chat Android documentation
- For more about Compose, some first impressions and recommended learning resources
Источник
Первое впечатление от Android Jetpack Compose
После того, как на Google IO 2019 я увидел доклад про Android Jetpack Compose, захотелось сразу же его попробовать. Тем более, что подход, реализованный в нём, очень напомнил Flutter, которым я интересовался ранее.
Сама библиотека Compose находится в пре-альфа стадии, поэтому доступно не так много документации и статей про нее. Далее я буду полагаться на несколько ресурсов, которые мне удалось найти, плюс открытые исходники библиотеки.
Вот эти ресурсы:
Что такое Android Jetpack Compose?
Раньше весь UI в Android был основан на классе View. Так повелось с первых дней Android. И в связи с этим накопилось множество легаси и архитектурных недостатков, которые могли бы быть улучшены. Но сделать это достаточно сложно, не сломав весь код, написанный на их основе.
За последние годы появилось множество новых концептов в мире клиентских приложений (включая веяния Frontend-а), поэтому команда Google пошла радикальным путём и переписала весь UI-уровень в Android с нуля. Так и появилась библиотека Android Jetpack Compose, включающая в себя концептуальные приёмы из React, Litho, Vue, Flutter и многих других.
Давайте пройдемся по некоторым особенностям существующего UI и сравним его с Compose.
1. Независимость от релизов Android
Существующий UI тесно связан с платформой. Когда появились первые компоненты Material Design, они работали только с Android 5 (API21) и выше. Для работы на старых версиях системы необходимо использовать Support Library.
Compose же входит в состав Jetpack, что делает его независимым от версий системы и возможным для использования даже в старых версиях Android (как минимум с API21).
2. Весь API на Kotlin
Раньше приходилось иметь дело с разными файлами, чтобы сделать UI. Мы описывали разметку в xml, а затем использовали Java/Kotlin код, чтобы заставить ее работать. Затем мы снова возвращались в другие xml-файлы для того чтобы задать темы, анимацию, навигацию,… И даже пытались писать код в xml (Data Binding).
Использование Kotlin позволяет писать UI в декларативном стиле прямо в коде вместо xml.
3. Composable = Композитный: использование композиции вместо наследования
Создание кастомных элементов UI может быть довольно громоздким. Нам необходимо унаследоваться от View или его потомка и позаботиться о многих важных свойствах перед тем, как он правильно заведется. Например, класс TextView содержит около 30 тысяч строк Java-кода. Это связано с тем, что он содержит множество лишней логики внутри себя, которую наследуют элементы-потомки.
Compose подошел с другой стороны, заменяя наследование композицией.
Padding как нельзя лучше подойдет для иллюстрации того, о чем речь:
В существующем UI для того, чтобы отрисовать TextView c отступами в 30dp :
нам нужно написать следующий код:
Это означает, что где-то внутри TextView.java или его суперклассов содержится логика, которая знает, как посчитать и отрисовать отступы.
Давайте посмотрим, как можно сделать то же самое в Compose:
Изменения
TextView стал просто Text() . Свойство android:padding превратилось в Padding , который оборачивает Text .
Преимущества
Таким образом, Text отвечает только за отрисовку непосредственно текста. Он не знает про то, как считать отступы. С другой стороны, Padding отвечает только за отступы и ничего больше. Он может быть использован вокруг любого другого элемента.
4. Однонаправленный поток данных
Однонаправленный поток данных является важным концептом, если мы говорим, например, про управление состоянием CheckBox в существующей системе UI. Когда пользователь тапает на CheckBox , его состояние становится checked = true : класс обновляет состояние View и вызывает callback из кода, который следит за изменением состояния.
Затем в самом коде, например, во ViewModel , вам нужно обновить соответствующую переменную state . Теперь у вас есть две копии нажатого состояния, которые могут создать проблемы. Например, изменение значения переменной state внутри ViewModel вызовет обновление CheckBox , что может закончиться бесконечным циклом. Чтобы избежать этого, нам придется придумывать какой-то костыль.
Использование Compose поможет решить эти проблемы, так как в его основе заложен принцип однонаправленности. Изменение состояния будет обрабатываться внутри фреймворка: мы просто отдаем модель данных внутрь. Кроме того, компонент в Compose теперь не меняет свое состояние самостоятельно. Вместо этого он только вызывает callback, и теперь это задача приложения изменить UI.
5. Улучшение отладки
Так как теперь весь UI написан на Kotlin, теперь можно дебажить UI. Я не попробовал это сам, но в подкасте говорили, что в Compose работают дебаггер и точки остановки.
Хватит слов, покажите код
Я знаю, хочется поскорее увидеть, как выглядит UI в коде (спойлер: очень похоже на Flutter, если вы пробовали писать на нем).
Мы начнем с создания нескольких простых View , затем сравним как они выглядят в существующем UI и в Compose.
1. FrameLayout vs Wrap + Padding + Background
Переиспользуем наш пример выше и попробуем сделать этот TextView с отступами в 30dp и бирюзовым фоном:
Теперь посмотрим на код, который делает то же самое в Compose:
Здесь появляется несколько новых вещей. Так как Text знает только про рендеринг текста, он не заботится об отступах и фоне. Поэтому, чтобы добавить их, нам нужно использовать три отдельные функции:
- DrawRectangle отрисовывает фон
- Padding отрисовывает отступы
- Wrap — функция, которая накладывает параметры друг на друга, как FrameLayout .
Легко. Но немного отличается от существующей UI-системы, к который мы все привыкли.
2. Вертикальный LinearLayout vs Column
Теперь попробуем сделать что-то эквивалентное нашему старому доброму LinearLayout .
Чтобы поместить два элемента один под другим, как на картинке ниже, мы можем использовать Column :
Код будет выглядеть так:
Вложенные в Column элемент будут расположены вертикально друг под другом.
2a. Отступы
Вероятно, вы заметили, что текст и кнопка расположены слишком близко к краю. Поэтому добавим Padding .
2b. Интервалы
Мы можем также добавить немного отступов между Text и Button :
Как выглядит наш экран теперь:
2c. Горизонтальный LinearLayout vs Row
Поместим вторую кнопку рядом с первой:
Внутри Row две кнопки будут расположены горизонтально. WidthSpacer добавляет расстояние между ними.
2d. Gravity vs Alignment
Выровняем наши элементы по центру, как это делает gravity в текущем UI. Чтобы показать diff, я закомментирую старые строки и заменю их новыми:
У нас получится:
С crossAxisAlignment = CrossAxisAlignment.Center вложенные элементы будут выравнены по горизонтали по центру. Мы должны также выставить Row параметр mainAxisSize = FlexSize.Min , похожий по поведению на layout_width = wrap_content , чтобы он не растягивался по всему экрану из-за дефолтного mainAxisSize = FlexSize.Max , который ведет себя как layout_width = match_parent .
2d. Замечание
Из того, что мы видели в примерах выше, можно заметить, что все элементы строятся композитно из отдельных функций: padding — отдельная функция, spacer — отдельная функция, вместо того, чтобы быть свойствами внутри Text , Button или Column .
Более сложные элементы, такие как RecyclerView или ConstraintLayout находятся в разработке: поэтому я не смог найти пример с ними в демонстрационных исходниках.
3.Стили и темы
Вы, вероятно, заметили, что кнопки выше по умолчанию фиолетовые. Это происходит потому, что они используют стили по умолчанию. Рассмотрим, как работают стили в Compose.
В примерах выше FormDemo помечена аннотацией @Composable . Теперь я покажу, как этот элемент используется в Activity :
Вместо функции setContentView() мы используем setContent() — функция-расширение из библиотеки Compose.kt .
CraneWrapper содержит дерево Compose и предоставляет доступ к Context , Density , FocusManager и TextInputService .
MaterialTheme позволяет кастомизировать тему для элементов.
Например, я могу изменить основной цвет темы (primary color) на каштановый следующим образом:
Теперь наш экран будет выглядеть так:
Другие цвета и шрифты, которы можно поменять: MaterialTheme.kt#57
Rally Activity содержит хороший пример, как можно кастомизировать тему: source code to RallyTheme.kt
Что посмотреть/почитать
Если вы хотите большего, вы можете собрать проект-образец по инструкции тут.
Как пишут пользователи Windows, сейчас не существует официального способа запустить Compose, но есть неофициальный гайд из kotlinlang Slack.
Вопросы про Compose можно задать разработчикам в канале #compose kotlinlang Slack.
Оставляйте другие ссылки в комментариях — самые полезные добавлю сюда.
Выводы
Разработка этой библиотеки идет полным ходом, поэтому любые интерфейсы, показанные здесь могут быть изменены. Остается еще множество вещей, про которые можно узнать в исходном коде, как например @Model и Unidirectional data flow (однонаправленный поток данных). Возможно, это тема для будущих статей.
Источник