From f75d061dc47b68f8e7bf0bdaced81b147ced32c5 Mon Sep 17 00:00:00 2001
From: Gian <47775302+gpunto@users.noreply.github.com>
Date: Thu, 4 Jun 2026 16:08:39 +0200
Subject: [PATCH] Update tutorial code to use Chat v7
---
build.gradle | 2 +-
samplejava/build.gradle | 18 ++++----
.../example/chattutorial/ChannelActivity.java | 40 +++++++++---------
.../chattutorial/ChannelActivity2.java | 40 +++++++++---------
.../chattutorial/ChannelActivity3.java | 42 ++++++++++---------
.../chattutorial/ChannelActivity4.java | 40 +++++++++---------
.../com/example/chattutorial/EdgeToEdge.java | 22 ++++++++++
.../chattutorial/ImgurAttachmentFactory.java | 25 +++++------
.../example/chattutorial/MainActivity.java | 24 ++++-------
.../src/main/res/layout/activity_channel.xml | 6 +--
.../main/res/layout/activity_channel_2.xml | 6 +--
.../main/res/layout/activity_channel_3.xml | 8 ++--
.../main/res/layout/activity_channel_4.xml | 8 ++--
samplejava/src/main/res/values/themes.xml | 2 +
samplekotlin/build.gradle | 13 +++---
.../example/chattutorial/ChannelActivity.kt | 34 ++++++++-------
.../example/chattutorial/ChannelActivity2.kt | 34 ++++++++-------
.../example/chattutorial/ChannelActivity3.kt | 36 ++++++++--------
.../example/chattutorial/ChannelActivity4.kt | 34 ++++++++-------
.../com/example/chattutorial/EdgeToEdge.kt | 16 +++++++
.../chattutorial/ImgurAttachmentFactory.kt | 9 ++--
.../com/example/chattutorial/MainActivity.kt | 26 ++++--------
.../src/main/res/layout/activity_channel.xml | 6 +--
.../main/res/layout/activity_channel_2.xml | 6 +--
.../main/res/layout/activity_channel_3.xml | 8 ++--
.../main/res/layout/activity_channel_4.xml | 8 ++--
samplekotlin/src/main/res/values/themes.xml | 2 +
27 files changed, 279 insertions(+), 236 deletions(-)
create mode 100644 samplejava/src/main/java/com/example/chattutorial/EdgeToEdge.java
create mode 100644 samplekotlin/src/main/java/com/example/chattutorial/EdgeToEdge.kt
diff --git a/build.gradle b/build.gradle
index 1442959..a19aed4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0"
}
}
diff --git a/samplejava/build.gradle b/samplejava/build.gradle
index 1d8d86b..c2d2fbe 100644
--- a/samplejava/build.gradle
+++ b/samplejava/build.gradle
@@ -3,13 +3,13 @@ plugins {
}
android {
- compileSdk 35
+ compileSdk 36
namespace "com.example.chattutorial"
defaultConfig {
applicationId "com.example.chattutorial"
- minSdk 21
- targetSdk 34
+ minSdk 24
+ targetSdk 36
versionCode 1
versionName "1.0"
@@ -27,14 +27,18 @@ android {
buildFeatures {
viewBinding true
}
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
}
dependencies {
- // Add new dependencies
- implementation "io.getstream:stream-chat-android-ui-components:6.28.0"
- implementation "io.getstream:stream-chat-android-offline:6.28.0"
+ // Add the dependency
+ implementation "io.getstream:stream-chat-android-ui-components:7.3.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7"
implementation "com.google.android.material:material:1.12.0"
implementation "androidx.activity:activity-ktx:1.10.1"
- implementation "io.coil-kt:coil:2.7.0"
+ implementation "io.coil-kt.coil3:coil:3.1.0"
}
diff --git a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity.java b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity.java
index 9084168..915c7cf 100644
--- a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity.java
+++ b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity.java
@@ -15,14 +15,14 @@
import io.getstream.chat.android.models.Message;
import io.getstream.chat.android.ui.common.state.messages.Edit;
import io.getstream.chat.android.ui.common.state.messages.MessageMode;
-import io.getstream.chat.android.ui.feature.messages.header.MessageListHeaderView;
+import io.getstream.chat.android.ui.feature.messages.header.ChannelHeaderView;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModelBinding;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModelBinding;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory;
public class ChannelActivity extends AppCompatActivity {
@@ -38,59 +38,61 @@ public static Intent newIntent(Context context, Channel channel) {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Step 0 - inflate binding
+ // inflate binding
ActivityChannelBinding binding = ActivityChannelBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ EdgeToEdge.applySystemBarInsetsAsPadding(binding.getRoot());
+
String cid = getIntent().getStringExtra(CID_KEY);
if (cid == null) {
throw new IllegalStateException("Specifying a channel id is required when starting ChannelActivity");
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- ViewModelProvider.Factory factory = new MessageListViewModelFactory.Builder(this)
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ ViewModelProvider.Factory factory = new ChannelViewModelFactory.Builder(this)
.cid(cid)
.build();
ViewModelProvider provider = new ViewModelProvider(this, factory);
- MessageListHeaderViewModel messageListHeaderViewModel = provider.get(MessageListHeaderViewModel.class);
+ ChannelHeaderViewModel channelHeaderViewModel = provider.get(ChannelHeaderViewModel.class);
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageComposerViewModel messageComposerViewModel = provider.get(MessageComposerViewModel.class);
// TODO set custom Imgur attachment factory
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- MessageListHeaderViewModelBinding.bind(messageListHeaderViewModel, binding.messageListHeaderView, this);
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ ChannelHeaderViewModelBinding.bind(channelHeaderViewModel, binding.channelHeaderView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageComposerViewModelBinding.bind(messageComposerViewModel, binding.messageComposerView, this);
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.getMode().observe(this, mode -> {
if (mode instanceof MessageMode.MessageThread) {
Message parentMessage = ((MessageMode.MessageThread) mode).getParentMessage();
- messageListHeaderViewModel.setActiveThread(parentMessage);
+ channelHeaderViewModel.setActiveThread(parentMessage);
messageComposerViewModel.setMessageMode(new MessageMode.MessageThread(parentMessage));
} else if (mode instanceof MessageMode.Normal) {
- messageListHeaderViewModel.resetThread();
+ channelHeaderViewModel.resetThread();
messageComposerViewModel.leaveThread();
}
});
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler(message -> {
messageComposerViewModel.performMessageAction(new Edit(message));
});
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.getState().observe(this, state -> {
if (state instanceof MessageListViewModel.State.NavigateUp) {
finish();
}
});
- // Step 6 - Handle back button behaviour correctly when you're in a thread
- MessageListHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
- binding.messageListHeaderView.setBackButtonClickListener(backHandler);
+ // Handle back button behaviour correctly when you're in a thread
+ ChannelHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
+ binding.channelHeaderView.setBackButtonClickListener(backHandler);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
diff --git a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity2.java b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity2.java
index 1e92cac..d0c5c96 100644
--- a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity2.java
+++ b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity2.java
@@ -18,15 +18,15 @@
import io.getstream.chat.android.models.Message;
import io.getstream.chat.android.ui.common.state.messages.Edit;
import io.getstream.chat.android.ui.common.state.messages.MessageMode;
-import io.getstream.chat.android.ui.feature.messages.header.MessageListHeaderView;
+import io.getstream.chat.android.ui.feature.messages.header.ChannelHeaderView;
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.attachment.AttachmentFactoryManager;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModelBinding;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModelBinding;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory;
public class ChannelActivity2 extends AppCompatActivity {
@@ -42,22 +42,24 @@ public static Intent newIntent(Context context, Channel channel) {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Step 0 - inflate binding
+ // inflate binding
ActivityChannel2Binding binding = ActivityChannel2Binding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ EdgeToEdge.applySystemBarInsetsAsPadding(binding.getRoot());
+
String cid = getIntent().getStringExtra(CID_KEY);
if (cid == null) {
throw new IllegalStateException("Specifying a channel id is required when starting ChannelActivity2");
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- ViewModelProvider.Factory factory = new MessageListViewModelFactory.Builder(this)
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ ViewModelProvider.Factory factory = new ChannelViewModelFactory.Builder(this)
.cid(cid)
.build();
ViewModelProvider provider = new ViewModelProvider(this, factory);
- MessageListHeaderViewModel messageListHeaderViewModel = provider.get(MessageListHeaderViewModel.class);
+ ChannelHeaderViewModel channelHeaderViewModel = provider.get(ChannelHeaderViewModel.class);
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageComposerViewModel messageComposerViewModel = provider.get(MessageComposerViewModel.class);
@@ -70,38 +72,38 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
AttachmentFactoryManager attachmentFactoryManager = new AttachmentFactoryManager(imgurAttachmentViewFactories);
binding.messageListView.setAttachmentFactoryManager(attachmentFactoryManager);
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- MessageListHeaderViewModelBinding.bind(messageListHeaderViewModel, binding.messageListHeaderView, this);
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ ChannelHeaderViewModelBinding.bind(channelHeaderViewModel, binding.channelHeaderView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageComposerViewModelBinding.bind(messageComposerViewModel, binding.messageComposerView, this);
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.getMode().observe(this, mode -> {
if (mode instanceof MessageMode.MessageThread) {
Message parentMessage = ((MessageMode.MessageThread) mode).getParentMessage();
- messageListHeaderViewModel.setActiveThread(parentMessage);
+ channelHeaderViewModel.setActiveThread(parentMessage);
messageComposerViewModel.setMessageMode(new MessageMode.MessageThread(parentMessage));
} else if (mode instanceof MessageMode.Normal) {
- messageListHeaderViewModel.resetThread();
+ channelHeaderViewModel.resetThread();
messageComposerViewModel.leaveThread();
}
});
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler(message -> {
messageComposerViewModel.performMessageAction(new Edit(message));
});
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.getState().observe(this, state -> {
if (state instanceof MessageListViewModel.State.NavigateUp) {
finish();
}
});
- // Step 6 - Handle back button behaviour correctly when you're in a thread
- MessageListHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
- binding.messageListHeaderView.setBackButtonClickListener(backHandler);
+ // Handle back button behaviour correctly when you're in a thread
+ ChannelHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
+ binding.channelHeaderView.setBackButtonClickListener(backHandler);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
diff --git a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity3.java b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity3.java
index a273b01..b778fc6 100644
--- a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity3.java
+++ b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity3.java
@@ -23,18 +23,18 @@
import io.getstream.chat.android.models.Channel;
import io.getstream.chat.android.models.Message;
import io.getstream.chat.android.models.TypingEvent;
-import io.getstream.chat.android.state.extensions.ChatClientExtensions;
+import io.getstream.chat.android.client.api.state.ChatClientExtensions;
import io.getstream.chat.android.ui.common.state.messages.Edit;
import io.getstream.chat.android.ui.common.state.messages.MessageMode;
-import io.getstream.chat.android.ui.feature.messages.header.MessageListHeaderView;
+import io.getstream.chat.android.ui.feature.messages.header.ChannelHeaderView;
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.attachment.AttachmentFactoryManager;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModelBinding;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModelBinding;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory;
import kotlinx.coroutines.flow.Flow;
public class ChannelActivity3 extends AppCompatActivity {
@@ -51,22 +51,24 @@ public static Intent newIntent(Context context, Channel channel) {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Step 0 - inflate binding
+ // inflate binding
ActivityChannel3Binding binding = ActivityChannel3Binding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ EdgeToEdge.applySystemBarInsetsAsPadding(binding.getRoot());
+
String cid = getIntent().getStringExtra(CID_KEY);
if (cid == null) {
throw new IllegalStateException("Specifying a channel id is required when starting ChannelActivity3");
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- ViewModelProvider.Factory factory = new MessageListViewModelFactory.Builder(this)
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ ViewModelProvider.Factory factory = new ChannelViewModelFactory.Builder(this)
.cid(cid)
.build();
ViewModelProvider provider = new ViewModelProvider(this, factory);
- MessageListHeaderViewModel messageListHeaderViewModel = provider.get(MessageListHeaderViewModel.class);
+ ChannelHeaderViewModel channelHeaderViewModel = provider.get(ChannelHeaderViewModel.class);
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageComposerViewModel messageComposerViewModel = provider.get(MessageComposerViewModel.class);
@@ -79,38 +81,38 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
AttachmentFactoryManager attachmentFactoryManager = new AttachmentFactoryManager(imgurAttachmentViewFactories);
binding.messageListView.setAttachmentFactoryManager(attachmentFactoryManager);
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- MessageListHeaderViewModelBinding.bind(messageListHeaderViewModel, binding.messageListHeaderView, this);
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ ChannelHeaderViewModelBinding.bind(channelHeaderViewModel, binding.channelHeaderView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageComposerViewModelBinding.bind(messageComposerViewModel, binding.messageComposerView, this);
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.getMode().observe(this, mode -> {
if (mode instanceof MessageMode.MessageThread) {
Message parentMessage = ((MessageMode.MessageThread) mode).getParentMessage();
- messageListHeaderViewModel.setActiveThread(parentMessage);
+ channelHeaderViewModel.setActiveThread(parentMessage);
messageComposerViewModel.setMessageMode(new MessageMode.MessageThread(parentMessage));
} else if (mode instanceof MessageMode.Normal) {
- messageListHeaderViewModel.resetThread();
+ channelHeaderViewModel.resetThread();
messageComposerViewModel.leaveThread();
}
});
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler(message -> {
messageComposerViewModel.performMessageAction(new Edit(message));
});
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.getState().observe(this, state -> {
if (state instanceof MessageListViewModel.State.NavigateUp) {
finish();
}
});
- // Step 6 - Handle back button behaviour correctly when you're in a thread
- MessageListHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
- binding.messageListHeaderView.setBackButtonClickListener(backHandler);
+ // Handle back button behaviour correctly when you're in a thread
+ ChannelHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
+ binding.channelHeaderView.setBackButtonClickListener(backHandler);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
diff --git a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity4.java b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity4.java
index a47e92f..f9e964d 100644
--- a/samplejava/src/main/java/com/example/chattutorial/ChannelActivity4.java
+++ b/samplejava/src/main/java/com/example/chattutorial/ChannelActivity4.java
@@ -26,15 +26,15 @@
import io.getstream.chat.android.models.User;
import io.getstream.chat.android.ui.common.state.messages.Edit;
import io.getstream.chat.android.ui.common.state.messages.MessageMode;
-import io.getstream.chat.android.ui.feature.messages.header.MessageListHeaderView;
+import io.getstream.chat.android.ui.feature.messages.header.ChannelHeaderView;
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.attachment.AttachmentFactoryManager;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModelBinding;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModelBinding;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel;
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelBinding;
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory;
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory;
public class ChannelActivity4 extends AppCompatActivity {
@@ -50,22 +50,24 @@ public static Intent newIntent(Context context, Channel channel) {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Step 0 - inflate binding
+ // inflate binding
ActivityChannel4Binding binding = ActivityChannel4Binding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ EdgeToEdge.applySystemBarInsetsAsPadding(binding.getRoot());
+
String cid = getIntent().getStringExtra(CID_KEY);
if (cid == null) {
throw new IllegalStateException("Specifying a channel id is required when starting ChannelActivity4");
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- ViewModelProvider.Factory factory = new MessageListViewModelFactory.Builder(this)
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ ViewModelProvider.Factory factory = new ChannelViewModelFactory.Builder(this)
.cid(cid)
.build();
ViewModelProvider provider = new ViewModelProvider(this, factory);
- MessageListHeaderViewModel messageListHeaderViewModel = provider.get(MessageListHeaderViewModel.class);
+ ChannelHeaderViewModel channelHeaderViewModel = provider.get(ChannelHeaderViewModel.class);
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageComposerViewModel messageComposerViewModel = provider.get(MessageComposerViewModel.class);
@@ -78,38 +80,38 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
AttachmentFactoryManager attachmentFactoryManager = new AttachmentFactoryManager(imgurAttachmentViewFactories);
binding.messageListView.setAttachmentFactoryManager(attachmentFactoryManager);
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- MessageListHeaderViewModelBinding.bind(messageListHeaderViewModel, binding.messageListHeaderView, this);
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ ChannelHeaderViewModelBinding.bind(channelHeaderViewModel, binding.channelHeaderView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageComposerViewModelBinding.bind(messageComposerViewModel, binding.messageComposerView, this);
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.getMode().observe(this, mode -> {
if (mode instanceof MessageMode.MessageThread) {
Message parentMessage = ((MessageMode.MessageThread) mode).getParentMessage();
- messageListHeaderViewModel.setActiveThread(parentMessage);
+ channelHeaderViewModel.setActiveThread(parentMessage);
messageComposerViewModel.setMessageMode(new MessageMode.MessageThread(parentMessage));
} else if (mode instanceof MessageMode.Normal) {
- messageListHeaderViewModel.resetThread();
+ channelHeaderViewModel.resetThread();
messageComposerViewModel.leaveThread();
}
});
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler(message -> {
messageComposerViewModel.performMessageAction(new Edit(message));
});
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.getState().observe(this, state -> {
if (state instanceof MessageListViewModel.State.NavigateUp) {
finish();
}
});
- // Step 6 - Handle back button behaviour correctly when you're in a thread
- MessageListHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
- binding.messageListHeaderView.setBackButtonClickListener(backHandler);
+ // Handle back button behaviour correctly when you're in a thread
+ ChannelHeaderView.OnClickListener backHandler = () -> messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
+ binding.channelHeaderView.setBackButtonClickListener(backHandler);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
diff --git a/samplejava/src/main/java/com/example/chattutorial/EdgeToEdge.java b/samplejava/src/main/java/com/example/chattutorial/EdgeToEdge.java
new file mode 100644
index 0000000..25a75cd
--- /dev/null
+++ b/samplejava/src/main/java/com/example/chattutorial/EdgeToEdge.java
@@ -0,0 +1,22 @@
+package com.example.chattutorial;
+
+import android.view.View;
+
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+public final class EdgeToEdge {
+
+ private EdgeToEdge() {}
+
+ public static void applySystemBarInsetsAsPadding(View view) {
+ ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()
+ );
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsetsCompat.CONSUMED;
+ });
+ }
+}
diff --git a/samplejava/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.java b/samplejava/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.java
index aaaa483..5322d84 100644
--- a/samplejava/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.java
+++ b/samplejava/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.java
@@ -11,8 +11,10 @@
import org.jetbrains.annotations.NotNull;
-import coil.Coil;
-import coil.request.ImageRequest;
+import coil3.SingletonImageLoader;
+import coil3.request.ImageRequest;
+import coil3.request.ImageRequestsKt;
+import coil3.request.ImageRequests_androidKt;
import io.getstream.chat.android.models.Attachment;
import io.getstream.chat.android.models.Message;
import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListListeners;
@@ -25,13 +27,13 @@
public class ImgurAttachmentFactory extends BaseAttachmentFactory {
- // Step 1 - Check whether the message contains an Imgur attachment
+ // Check whether the message contains an Imgur attachment
@Override
public boolean canHandle(@NonNull Message message) {
return containsImgurAttachments(message) != null;
}
- // Step 2 - Create the ViewHolder that will be used to display the Imgur logo
+ // Create the ViewHolder that will be used to display the Imgur logo
// over Imgur attachments
@NonNull
@Override
@@ -73,14 +75,13 @@ public ImgurAttachmentViewHolder(AttachmentImgurBinding binding,
binding.ivMediaThumb.setShapeAppearanceModel(shapeAppearanceModel);
if (imgurAttachment != null) {
- ImageRequest imageRequest = new ImageRequest.Builder(binding.getRoot().getContext())
- .data(imgurAttachment.getImageUrl())
- .allowHardware(false)
- .crossfade(true)
- .placeholder(io.getstream.chat.android.ui.R.drawable.stream_ui_picture_placeholder)
- .target(binding.ivMediaThumb)
- .build();
- Coil.imageLoader(binding.getRoot().getContext()).enqueue(imageRequest);
+ ImageRequest.Builder requestBuilder = new ImageRequest.Builder(binding.getRoot().getContext())
+ .data(imgurAttachment.getImageUrl());
+ ImageRequests_androidKt.allowHardware(requestBuilder, false);
+ ImageRequestsKt.crossfade(requestBuilder, true);
+ ImageRequests_androidKt.placeholder(requestBuilder, io.getstream.chat.android.ui.R.drawable.stream_ui_picture_placeholder);
+ ImageRequests_androidKt.target(requestBuilder, binding.ivMediaThumb);
+ SingletonImageLoader.get(binding.getRoot().getContext()).enqueue(requestBuilder.build());
}
}
}
diff --git a/samplejava/src/main/java/com/example/chattutorial/MainActivity.java b/samplejava/src/main/java/com/example/chattutorial/MainActivity.java
index 4bd42d6..8acca29 100644
--- a/samplejava/src/main/java/com/example/chattutorial/MainActivity.java
+++ b/samplejava/src/main/java/com/example/chattutorial/MainActivity.java
@@ -16,9 +16,6 @@
import io.getstream.chat.android.models.FilterObject;
import io.getstream.chat.android.models.Filters;
import io.getstream.chat.android.models.User;
-import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory;
-import io.getstream.chat.android.state.plugin.config.StatePluginConfig;
-import io.getstream.chat.android.state.plugin.factory.StreamStatePluginFactory;
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModel;
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModelBinding;
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModelFactory;
@@ -28,25 +25,18 @@ public final class MainActivity extends AppCompatActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Step 0 - inflate binding
+ // Inflate binding for layout
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
- // Step 1 - Set up the OfflinePlugin for offline storage
- StreamOfflinePluginFactory streamOfflinePluginFactory = new StreamOfflinePluginFactory(
- getApplicationContext()
- );
- StreamStatePluginFactory streamStatePluginFactory = new StreamStatePluginFactory(
- new StatePluginConfig(true, true), this
- );
+ EdgeToEdge.applySystemBarInsetsAsPadding(binding.getRoot());
- // Step 2 - Set up the client for API calls with the plugin for offline storage
+ // Set up the client for API calls with offline storage and state management
ChatClient client = new ChatClient.Builder("uun7ywwamhs9", getApplicationContext())
- .withPlugins(streamOfflinePluginFactory, streamStatePluginFactory)
.logLevel(ChatLogLevel.ALL) // Set to NOTHING in prod
.build();
- // Step 3 - Authenticate and connect the user
+ // Authenticate and connect the user
User user = new User.Builder()
.withId("tutorial-droid")
.withName("Tutorial Droid")
@@ -57,7 +47,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
user,
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidHV0b3JpYWwtZHJvaWQifQ.WwfBzU1GZr0brt_fXnqKdKhz3oj0rbDUm2DqJO_SS5U"
).enqueue(result -> {
- // Step 4 - Set the channel list filter and order
+ // Set the channel list filter and order
// This can be read as requiring only channels whose "type" is "messaging" AND
// whose "members" include our "user.id"
FilterObject filter = Filters.and(
@@ -73,8 +63,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
ChannelListViewModel channelsViewModel =
new ViewModelProvider(this, factory).get(ChannelListViewModel.class);
- // Step 5 - Connect the ChannelListViewModel to the ChannelListView, loose
- // coupling makes it easy to customize
+ // Connect the ChannelListViewModel to the ChannelListView, loose
+ // coupling makes it easy to customize
ChannelListViewModelBinding.bind(channelsViewModel, binding.channelListView, this);
binding.channelListView.setChannelItemClickListener(
channel -> startActivity(ChannelActivity4.newIntent(this, channel))
diff --git a/samplejava/src/main/res/layout/activity_channel.xml b/samplejava/src/main/res/layout/activity_channel.xml
index db1fa0d..9d2185a 100644
--- a/samplejava/src/main/res/layout/activity_channel.xml
+++ b/samplejava/src/main/res/layout/activity_channel.xml
@@ -4,8 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
@color/black
- ?attr/colorPrimaryVariant
+ - true
+ - true
diff --git a/samplekotlin/build.gradle b/samplekotlin/build.gradle
index 78bd1f1..988141f 100644
--- a/samplekotlin/build.gradle
+++ b/samplekotlin/build.gradle
@@ -4,13 +4,13 @@ plugins {
}
android {
- compileSdk 35
+ compileSdk 36
namespace "com.example.chattutorial"
defaultConfig {
applicationId "com.example.chattutorial"
- minSdk 21
- targetSdk 34
+ minSdk 24
+ targetSdk 36
versionCode 1
versionName "1.0"
}
@@ -31,11 +31,10 @@ android {
}
dependencies {
- // Add new dependencies
- implementation "io.getstream:stream-chat-android-ui-components:6.28.0"
- implementation "io.getstream:stream-chat-android-offline:6.28.0"
+ // Add the dependency
+ implementation "io.getstream:stream-chat-android-ui-components:7.3.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7"
implementation "com.google.android.material:material:1.12.0"
implementation "androidx.activity:activity-ktx:1.10.1"
- implementation "io.coil-kt:coil:2.7.0"
+ implementation "io.coil-kt.coil3:coil:3.1.0"
}
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity.kt b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity.kt
index e09f3b9..c26a6b0 100644
--- a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity.kt
+++ b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity.kt
@@ -11,9 +11,9 @@ import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.MessageMode
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory
import io.getstream.chat.android.ui.viewmodel.messages.bindView
class ChannelActivity : AppCompatActivity() {
@@ -23,60 +23,62 @@ class ChannelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // Step 0 - inflate binding
+ // inflate binding
binding = ActivityChannelBinding.inflate(layoutInflater)
setContentView(binding.root)
+ binding.root.applySystemBarInsetsAsPadding()
+
val cid = checkNotNull(intent.getStringExtra(CID_KEY)) {
"Specifying a channel id is required when starting ChannelActivity"
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- val factory = MessageListViewModelFactory(this, cid)
- val messageListHeaderViewModel: MessageListHeaderViewModel by viewModels { factory }
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ val factory = ChannelViewModelFactory(this, cid)
+ val channelHeaderViewModel: ChannelHeaderViewModel by viewModels { factory }
val messageListViewModel: MessageListViewModel by viewModels { factory }
val messageComposerViewModel: MessageComposerViewModel by viewModels { factory }
// TODO set custom Imgur attachment factory
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- messageListHeaderViewModel.bindView(binding.messageListHeaderView, this)
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ channelHeaderViewModel.bindView(binding.channelHeaderView, this)
messageListViewModel.bindView(binding.messageListView, this)
messageComposerViewModel.bindView(binding.messageComposerView, this)
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.mode.observe(this) { mode ->
when (mode) {
is MessageMode.MessageThread -> {
- messageListHeaderViewModel.setActiveThread(mode.parentMessage)
+ channelHeaderViewModel.setActiveThread(mode.parentMessage)
messageComposerViewModel.setMessageMode(MessageMode.MessageThread(mode.parentMessage))
}
is MessageMode.Normal -> {
- messageListHeaderViewModel.resetThread()
+ channelHeaderViewModel.resetThread()
messageComposerViewModel.leaveThread()
}
}
}
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler { message ->
messageComposerViewModel.performMessageAction(Edit(message))
}
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.state.observe(this) { state ->
if (state is MessageListViewModel.State.NavigateUp) {
finish()
}
}
- // Step 6 - Handle back button behaviour correctly when you're in a thread
+ // Handle back button behaviour correctly when you're in a thread
val backHandler = {
messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
}
- binding.messageListHeaderView.setBackButtonClickListener(backHandler)
+ binding.channelHeaderView.setBackButtonClickListener(backHandler)
onBackPressedDispatcher.addCallback(this) {
backHandler()
}
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity2.kt b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity2.kt
index 49a63e8..60a3395 100644
--- a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity2.kt
+++ b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity2.kt
@@ -12,9 +12,9 @@ import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.MessageMode
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.attachment.AttachmentFactoryManager
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory
import io.getstream.chat.android.ui.viewmodel.messages.bindView
class ChannelActivity2 : AppCompatActivity() {
@@ -24,18 +24,20 @@ class ChannelActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // Step 0 - inflate binding
+ // inflate binding
binding = ActivityChannel2Binding.inflate(layoutInflater)
setContentView(binding.root)
+ binding.root.applySystemBarInsetsAsPadding()
+
val cid = checkNotNull(intent.getStringExtra(CID_KEY)) {
"Specifying a channel id is required when starting ChannelActivity2"
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- val factory = MessageListViewModelFactory(this, cid)
- val messageListHeaderViewModel: MessageListHeaderViewModel by viewModels { factory }
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ val factory = ChannelViewModelFactory(this, cid)
+ val channelHeaderViewModel: ChannelHeaderViewModel by viewModels { factory }
val messageListViewModel: MessageListViewModel by viewModels { factory }
val messageComposerViewModel: MessageComposerViewModel by viewModels { factory }
@@ -44,43 +46,43 @@ class ChannelActivity2 : AppCompatActivity() {
val attachmentViewFactory = AttachmentFactoryManager(listOf(imgurAttachmentViewFactory))
binding.messageListView.setAttachmentFactoryManager(attachmentViewFactory)
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- messageListHeaderViewModel.bindView(binding.messageListHeaderView, this)
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ channelHeaderViewModel.bindView(binding.channelHeaderView, this)
messageListViewModel.bindView(binding.messageListView, this)
messageComposerViewModel.bindView(binding.messageComposerView, this)
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.mode.observe(this) { mode ->
when (mode) {
is MessageMode.MessageThread -> {
- messageListHeaderViewModel.setActiveThread(mode.parentMessage)
+ channelHeaderViewModel.setActiveThread(mode.parentMessage)
messageComposerViewModel.setMessageMode(MessageMode.MessageThread(mode.parentMessage))
}
is MessageMode.Normal -> {
- messageListHeaderViewModel.resetThread()
+ channelHeaderViewModel.resetThread()
messageComposerViewModel.leaveThread()
}
}
}
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler { message ->
messageComposerViewModel.performMessageAction(Edit(message))
}
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.state.observe(this) { state ->
if (state is MessageListViewModel.State.NavigateUp) {
finish()
}
}
- // Step 6 - Handle back button behaviour correctly when you're in a thread
+ // Handle back button behaviour correctly when you're in a thread
val backHandler = {
messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
}
- binding.messageListHeaderView.setBackButtonClickListener(backHandler)
+ binding.channelHeaderView.setBackButtonClickListener(backHandler)
onBackPressedDispatcher.addCallback(this) {
backHandler()
}
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity3.kt b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity3.kt
index 5239266..e3ddcf1 100644
--- a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity3.kt
+++ b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity3.kt
@@ -12,14 +12,14 @@ import androidx.lifecycle.repeatOnLifecycle
import com.example.chattutorial.databinding.ActivityChannel3Binding
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.models.Channel
-import io.getstream.chat.android.state.extensions.watchChannelAsState
+import io.getstream.chat.android.client.api.state.watchChannelAsState
import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.MessageMode
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.attachment.AttachmentFactoryManager
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory
import io.getstream.chat.android.ui.viewmodel.messages.bindView
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
@@ -32,18 +32,20 @@ class ChannelActivity3 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // Step 0 - inflate binding
+ // inflate binding
binding = ActivityChannel3Binding.inflate(layoutInflater)
setContentView(binding.root)
+ binding.root.applySystemBarInsetsAsPadding()
+
val cid = checkNotNull(intent.getStringExtra(CID_KEY)) {
"Specifying a channel id is required when starting ChannelActivity3"
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- val factory = MessageListViewModelFactory(this, cid)
- val messageListHeaderViewModel: MessageListHeaderViewModel by viewModels { factory }
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ val factory = ChannelViewModelFactory(this, cid)
+ val channelHeaderViewModel: ChannelHeaderViewModel by viewModels { factory }
val messageListViewModel: MessageListViewModel by viewModels { factory }
val messageComposerViewModel: MessageComposerViewModel by viewModels { factory }
@@ -52,43 +54,43 @@ class ChannelActivity3 : AppCompatActivity() {
val attachmentViewFactory = AttachmentFactoryManager(listOf(imgurAttachmentViewFactory))
binding.messageListView.setAttachmentFactoryManager(attachmentViewFactory)
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- messageListHeaderViewModel.bindView(binding.messageListHeaderView, this)
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ channelHeaderViewModel.bindView(binding.channelHeaderView, this)
messageListViewModel.bindView(binding.messageListView, this)
messageComposerViewModel.bindView(binding.messageComposerView, this)
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.mode.observe(this) { mode ->
when (mode) {
is MessageMode.MessageThread -> {
- messageListHeaderViewModel.setActiveThread(mode.parentMessage)
+ channelHeaderViewModel.setActiveThread(mode.parentMessage)
messageComposerViewModel.setMessageMode(MessageMode.MessageThread(mode.parentMessage))
}
is MessageMode.Normal -> {
- messageListHeaderViewModel.resetThread()
+ channelHeaderViewModel.resetThread()
messageComposerViewModel.leaveThread()
}
}
}
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler { message ->
messageComposerViewModel.performMessageAction(Edit(message))
}
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.state.observe(this) { state ->
if (state is MessageListViewModel.State.NavigateUp) {
finish()
}
}
- // Step 6 - Handle back button behaviour correctly when you're in a thread
+ // Handle back button behaviour correctly when you're in a thread
val backHandler = {
messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
}
- binding.messageListHeaderView.setBackButtonClickListener(backHandler)
+ binding.channelHeaderView.setBackButtonClickListener(backHandler)
onBackPressedDispatcher.addCallback(this) {
backHandler()
}
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity4.kt b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity4.kt
index 8d810cc..f54bdf4 100644
--- a/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity4.kt
+++ b/samplekotlin/src/main/java/com/example/chattutorial/ChannelActivity4.kt
@@ -16,9 +16,9 @@ import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.MessageMode
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.attachment.AttachmentFactoryManager
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelHeaderViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel
-import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory
+import io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory
import io.getstream.chat.android.ui.viewmodel.messages.bindView
class ChannelActivity4 : AppCompatActivity() {
@@ -28,18 +28,20 @@ class ChannelActivity4 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // Step 0 - inflate binding
+ // inflate binding
binding = ActivityChannel4Binding.inflate(layoutInflater)
setContentView(binding.root)
+ binding.root.applySystemBarInsetsAsPadding()
+
val cid = checkNotNull(intent.getStringExtra(CID_KEY)) {
"Specifying a channel id is required when starting ChannelActivity4"
}
- // Step 1 - Create three separate ViewModels for the views so it's easy
- // to customize them individually
- val factory = MessageListViewModelFactory(this, cid)
- val messageListHeaderViewModel: MessageListHeaderViewModel by viewModels { factory }
+ // Create three separate ViewModels for the views so it's easy
+ // to customize them individually
+ val factory = ChannelViewModelFactory(this, cid)
+ val channelHeaderViewModel: ChannelHeaderViewModel by viewModels { factory }
val messageListViewModel: MessageListViewModel by viewModels { factory }
val messageComposerViewModel: MessageComposerViewModel by viewModels { factory }
@@ -48,43 +50,43 @@ class ChannelActivity4 : AppCompatActivity() {
val attachmentViewFactory = AttachmentFactoryManager(listOf(imgurAttachmentViewFactory))
binding.messageListView.setAttachmentFactoryManager(attachmentViewFactory)
- // Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
- messageListHeaderViewModel.bindView(binding.messageListHeaderView, this)
+ // Bind the view and ViewModels, they are loosely coupled so it's easy to customize
+ channelHeaderViewModel.bindView(binding.channelHeaderView, this)
messageListViewModel.bindView(binding.messageListView, this)
messageComposerViewModel.bindView(binding.messageComposerView, this)
- // Step 3 - Let both MessageListHeaderView and MessageComposerView know when we open a thread
+ // Let both ChannelHeaderView and MessageComposerView know when we open a thread
messageListViewModel.mode.observe(this) { mode ->
when (mode) {
is MessageMode.MessageThread -> {
- messageListHeaderViewModel.setActiveThread(mode.parentMessage)
+ channelHeaderViewModel.setActiveThread(mode.parentMessage)
messageComposerViewModel.setMessageMode(MessageMode.MessageThread(mode.parentMessage))
}
is MessageMode.Normal -> {
- messageListHeaderViewModel.resetThread()
+ channelHeaderViewModel.resetThread()
messageComposerViewModel.leaveThread()
}
}
}
- // Step 4 - Let the message input know when we are editing a message
+ // Let the message input know when we are editing a message
binding.messageListView.setMessageEditHandler { message ->
messageComposerViewModel.performMessageAction(Edit(message))
}
- // Step 5 - Handle navigate up state
+ // Handle navigate up state
messageListViewModel.state.observe(this) { state ->
if (state is MessageListViewModel.State.NavigateUp) {
finish()
}
}
- // Step 6 - Handle back button behaviour correctly when you're in a thread
+ // Handle back button behaviour correctly when you're in a thread
val backHandler = {
messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
}
- binding.messageListHeaderView.setBackButtonClickListener(backHandler)
+ binding.channelHeaderView.setBackButtonClickListener(backHandler)
onBackPressedDispatcher.addCallback(this) {
backHandler()
}
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/EdgeToEdge.kt b/samplekotlin/src/main/java/com/example/chattutorial/EdgeToEdge.kt
new file mode 100644
index 0000000..c2f63a9
--- /dev/null
+++ b/samplekotlin/src/main/java/com/example/chattutorial/EdgeToEdge.kt
@@ -0,0 +1,16 @@
+package com.example.chattutorial
+
+import android.view.View
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+
+fun View.applySystemBarInsetsAsPadding() {
+ ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
+ val insets = windowInsets.getInsets(
+ WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()
+ )
+ view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)
+ WindowInsetsCompat.CONSUMED
+ }
+}
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.kt b/samplekotlin/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.kt
index d1c9d33..2559583 100644
--- a/samplekotlin/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.kt
+++ b/samplekotlin/src/main/java/com/example/chattutorial/ImgurAttachmentFactory.kt
@@ -2,7 +2,10 @@ package com.example.chattutorial
import android.view.LayoutInflater
import android.view.ViewGroup
-import coil.load
+import coil3.load
+import coil3.request.allowHardware
+import coil3.request.crossfade
+import coil3.request.placeholder
import com.example.chattutorial.databinding.AttachmentImgurBinding
import io.getstream.chat.android.models.Attachment
import io.getstream.chat.android.models.Message
@@ -13,13 +16,13 @@ import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.att
/** A custom attachment factory to show an imgur logo if the attachment URL is an imgur image. */
class ImgurAttachmentFactory : AttachmentFactory {
- // Step 1 - Check whether the message contains an Imgur attachment
+ // Check whether the message contains an Imgur attachment
override fun canHandle(message: Message): Boolean {
val imgurAttachment = message.attachments.firstOrNull { it.isImgurAttachment() }
return imgurAttachment != null
}
- // Step 2 - Create the ViewHolder that will be used to display the Imgur logo
+ // Create the ViewHolder that will be used to display the Imgur logo
// over Imgur attachments
override fun createViewHolder(
message: Message,
diff --git a/samplekotlin/src/main/java/com/example/chattutorial/MainActivity.kt b/samplekotlin/src/main/java/com/example/chattutorial/MainActivity.kt
index 2a63ab1..3f30901 100644
--- a/samplekotlin/src/main/java/com/example/chattutorial/MainActivity.kt
+++ b/samplekotlin/src/main/java/com/example/chattutorial/MainActivity.kt
@@ -9,9 +9,6 @@ import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.logger.ChatLogLevel
import io.getstream.chat.android.models.Filters
import io.getstream.chat.android.models.User
-import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory
-import io.getstream.chat.android.state.plugin.config.StatePluginConfig
-import io.getstream.chat.android.state.plugin.factory.StreamStatePluginFactory
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModel
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModelFactory
import io.getstream.chat.android.ui.viewmodel.channels.bindView
@@ -23,27 +20,18 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // Step 0 - inflate binding
+ // Inflate binding for layout
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
- // Step 1 - Set up the OfflinePlugin for offline storage
- val offlinePluginFactory = StreamOfflinePluginFactory(appContext = this)
- val statePluginFactory = StreamStatePluginFactory(
- config = StatePluginConfig(
- backgroundSyncEnabled = true,
- userPresence = true,
- ),
- appContext = this,
- )
+ binding.root.applySystemBarInsetsAsPadding()
- // Step 2 - Set up the client for API calls with the plugin for offline storage
+ // Set up the client for API calls with offline storage and state management
val client = ChatClient.Builder("uun7ywwamhs9", applicationContext)
- .withPlugins(offlinePluginFactory, statePluginFactory)
.logLevel(ChatLogLevel.ALL) // Set to NOTHING in prod
.build()
- // Step 3 - Authenticate and connect the user
+ // Authenticate and connect the user
val user = User(
id = "tutorial-droid",
name = "Tutorial Droid",
@@ -54,7 +42,7 @@ class MainActivity : AppCompatActivity() {
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidHV0b3JpYWwtZHJvaWQifQ.WwfBzU1GZr0brt_fXnqKdKhz3oj0rbDUm2DqJO_SS5U"
).enqueue {
if (it.isSuccess) {
- // Step 4 - Set the channel list filter and order
+ // Set the channel list filter and order
// This can be read as requiring only channels whose "type" is "messaging" AND
// whose "members" include our "user.id"
val filter = Filters.and(
@@ -65,8 +53,8 @@ class MainActivity : AppCompatActivity() {
ChannelListViewModelFactory(filter, ChannelListViewModel.DEFAULT_SORT)
val viewModel: ChannelListViewModel by viewModels { viewModelFactory }
- // Step 5 - Connect the ChannelListViewModel to the ChannelListView, loose
- // coupling makes it easy to customize
+ // Connect the ChannelListViewModel to the ChannelListView, loose
+ // coupling makes it easy to customize
viewModel.bindView(binding.channelListView, this)
binding.channelListView.setChannelItemClickListener { channel ->
startActivity(ChannelActivity4.newIntent(this, channel))
diff --git a/samplekotlin/src/main/res/layout/activity_channel.xml b/samplekotlin/src/main/res/layout/activity_channel.xml
index db1fa0d..9d2185a 100644
--- a/samplekotlin/src/main/res/layout/activity_channel.xml
+++ b/samplekotlin/src/main/res/layout/activity_channel.xml
@@ -4,8 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
-
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
+ app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />
@color/black
- ?attr/colorPrimaryVariant
+ - true
+ - true