Skip to content

Update Tutorial to SDK 5.2.0 #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions samplejava/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ android {

dependencies {
// Add new dependencies
implementation "io.getstream:stream-chat-android-ui-components:4.28.2"
implementation "com.google.android.material:material:1.4.0"
implementation "io.getstream:stream-chat-android-ui-components:5.2.0"
implementation "com.google.android.material:material:1.6.0"
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "io.coil-kt:coil:1.4.0"

// We use the ktx dependency for transforming StateFlow into LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {

// Step 2 - Bind the view and ViewModels, they are loosely coupled so it's easy to customize
MessageListHeaderViewModelBinding.bind(messageListHeaderViewModel, binding.messageListHeaderView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this, true);
MessageInputViewModelBinding.bind(messageInputViewModel, binding.messageInputView, this);

// Step 3 - Let both MessageListHeaderView and MessageInputView know when we open a thread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel.Mode.Thread;
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel.State.NavigateUp;

import java.util.ArrayList;
import java.util.List;

import io.getstream.chat.android.client.models.Channel;
import io.getstream.chat.android.client.models.Message;
import io.getstream.chat.android.ui.message.input.viewmodel.MessageInputViewModelBinding;
import io.getstream.chat.android.ui.message.list.adapter.viewholder.attachment.AttachmentFactoryManager;
import io.getstream.chat.android.ui.message.list.header.MessageListHeaderView;
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModel;
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModelBinding;
Expand Down Expand Up @@ -56,12 +60,18 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageInputViewModel messageInputViewModel = provider.get(MessageInputViewModel.class);

// Set view factory for Imgur attachments
binding.messageListView.setAttachmentViewFactory(new ImgurAttachmentViewFactory());
// Set a view factory manager for Imgur attachments
ImgurAttachmentFactory imgurAttachmentFactory = new ImgurAttachmentFactory();

List<ImgurAttachmentFactory> imgurAttachmentViewFactories = new ArrayList<ImgurAttachmentFactory>();
imgurAttachmentViewFactories.add(imgurAttachmentFactory);

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);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this, true);
MessageInputViewModelBinding.bind(messageInputViewModel, binding.messageInputView, this);

// Step 3 - Let both MessageListHeaderView and MessageInputView know when we open a thread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;

import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.FlowLiveDataConversions;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModelProvider;

import com.example.chattutorial.databinding.ActivityChannel3Binding;
Expand All @@ -18,20 +20,25 @@
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel.Mode.Thread;
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel.State.NavigateUp;

import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;

import io.getstream.chat.android.client.ChatClient;
import io.getstream.chat.android.client.models.Channel;
import io.getstream.chat.android.client.models.Message;
import io.getstream.chat.android.client.models.User;
import io.getstream.chat.android.livedata.ChatDomain;
import io.getstream.chat.android.livedata.controller.ChannelController;
import io.getstream.chat.android.client.models.TypingEvent;
import io.getstream.chat.android.offline.extensions.ChatClientExtensions;
import io.getstream.chat.android.offline.plugin.state.channel.ChannelState;
import io.getstream.chat.android.ui.message.input.viewmodel.MessageInputViewModelBinding;
import io.getstream.chat.android.ui.message.list.adapter.viewholder.attachment.AttachmentFactoryManager;
import io.getstream.chat.android.ui.message.list.header.MessageListHeaderView;
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModel;
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModelBinding;
import io.getstream.chat.android.ui.message.list.viewmodel.MessageListViewModelBinding;
import io.getstream.chat.android.ui.message.list.viewmodel.factory.MessageListViewModelFactory;
import kotlinx.coroutines.Dispatchers;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.FlowKt;

public class ChannelActivity3 extends AppCompatActivity {

Expand Down Expand Up @@ -64,12 +71,18 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageInputViewModel messageInputViewModel = provider.get(MessageInputViewModel.class);

// Set view factory for Imgur attachments
binding.messageListView.setAttachmentViewFactory(new ImgurAttachmentViewFactory());
// Set a view factory manager for Imgur attachments
ImgurAttachmentFactory imgurAttachmentFactory = new ImgurAttachmentFactory();

List<ImgurAttachmentFactory> imgurAttachmentViewFactories = new ArrayList<ImgurAttachmentFactory>();
imgurAttachmentViewFactories.add(imgurAttachmentFactory);

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);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this, true);
MessageInputViewModelBinding.bind(messageInputViewModel, binding.messageInputView, this);

// Step 3 - Let both MessageListHeaderView and MessageInputView know when we open a thread
Expand Down Expand Up @@ -107,29 +120,43 @@ public void handleOnBackPressed() {
});

// Custom typing info header bar
TextView typingHeaderView = findViewById(R.id.typingHeaderView);
String nobodyTyping = "nobody is typing";
typingHeaderView.setText(nobodyTyping);

// Obtain a ChannelController
ChatDomain.instance()
.getChannelController(cid)
.enqueue((result) -> {
ChannelController channelController = result.data();

// Observe typing users
channelController.getTyping().observe(this, typingState -> {
if (typingState.getUsers().isEmpty()) {
typingHeaderView.setText(nobodyTyping);
} else {
List<String> userNames = new LinkedList<>();
for (User user : typingState.getUsers()) {
userNames.add(user.getName());
}
String typing = "typing: " + TextUtils.join(", ", userNames);
typingHeaderView.setText(typing);
}
});
});
binding.typingHeaderView.setText(nobodyTyping);

// Observe typing events and update typing header depending on its state.
Flow<ChannelState> channelStateFlow = ChatClientExtensions.watchChannelAsState(ChatClient.instance(), cid, 30);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very clunky, but we currently don't expose any of our state classes (ChannelState, GlobalState, ...) as LiveData.

It was either this or suggesting that Java users use Coroutines, which I feel is a bigger unknown in the Java world + the syntax would still be clunky since Flow operators are written as extensions.

LiveData<TypingEvent> typingEventLiveData = Transformations.switchMap(
FlowLiveDataConversions.asLiveData(channelStateFlow),
channelState -> FlowLiveDataConversions.asLiveData(channelState.getTyping())
);

typingEventLiveData.observe(this, typingEvent -> {
String headerText;

if (typingEvent.getUsers().size() != 0) {
headerText = "typing: " + joinTypingUpdatesToUserNames(typingEvent);
} else {
headerText = nobodyTyping;
}

binding.typingHeaderView.setText(headerText);
});
}

// Helper method that transforms typing updates into a string
// containing typing member's names
@NonNull
private String joinTypingUpdatesToUserNames(@NonNull TypingEvent typingEvent) {
Copy link
Contributor Author

@MarinTolic MarinTolic May 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to do this manually because String.join() is not compatible with Android API 21 without desugaring.

StringBuilder joinedString = new StringBuilder();

for (int i = 0; i < typingEvent.getUsers().size(); i++) {
if (i < typingEvent.getUsers().size() - 1) {
joinedString.append(typingEvent.getUsers().get(i).getName()).append(", ");
} else {
joinedString.append(typingEvent.getUsers().get(i).getName());
}
}

return joinedString.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel.Mode.Thread;
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel.State.NavigateUp;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import io.getstream.chat.android.client.ChatClient;
Expand All @@ -28,6 +30,7 @@
import io.getstream.chat.android.client.models.Message;
import io.getstream.chat.android.client.models.User;
import io.getstream.chat.android.ui.message.input.viewmodel.MessageInputViewModelBinding;
import io.getstream.chat.android.ui.message.list.adapter.viewholder.attachment.AttachmentFactoryManager;
import io.getstream.chat.android.ui.message.list.header.MessageListHeaderView;
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModel;
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModelBinding;
Expand Down Expand Up @@ -65,12 +68,18 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageInputViewModel messageInputViewModel = provider.get(MessageInputViewModel.class);

// Set view factory for Imgur attachments
binding.messageListView.setAttachmentViewFactory(new ImgurAttachmentViewFactory());
// Set a view factory manager for Imgur attachments
ImgurAttachmentFactory imgurAttachmentFactory = new ImgurAttachmentFactory();

List<ImgurAttachmentFactory> imgurAttachmentViewFactories = new ArrayList<ImgurAttachmentFactory>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, List.of() not supported by API 21 without desugaring, making this feel clunky.

imgurAttachmentViewFactories.add(imgurAttachmentFactory);

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);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this);
MessageListViewModelBinding.bind(messageListViewModel, binding.messageListView, this, true);
MessageInputViewModelBinding.bind(messageInputViewModel, binding.messageInputView, this);

// Step 3 - Let both MessageListHeaderView and MessageInputView know when we open a thread
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.example.chattutorial;

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.example.chattutorial.databinding.AttachmentImgurBinding;
import com.google.android.material.shape.ShapeAppearanceModel;

import org.jetbrains.annotations.NotNull;

import coil.Coil;
import coil.request.ImageRequest;
import io.getstream.chat.android.client.models.Attachment;
import io.getstream.chat.android.client.models.Message;
import io.getstream.chat.android.ui.message.list.adapter.MessageListListenerContainer;
import io.getstream.chat.android.ui.message.list.adapter.viewholder.attachment.AttachmentFactory;
import io.getstream.chat.android.ui.message.list.adapter.viewholder.attachment.InnerAttachmentViewHolder;

/** A custom attachment factory to show an imgur logo if the attachment URL is an imgur image. **/
public class ImgurAttachmentFactory implements AttachmentFactory {


// Step 1 - 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
// over Imgur attachments
@NonNull
@Override
public InnerAttachmentViewHolder createViewHolder(
@NonNull Message message,
@Nullable MessageListListenerContainer listeners,
@NonNull ViewGroup parent
) {
Attachment imgurAttachment = containsImgurAttachments(message);

AttachmentImgurBinding attachmentImgurBinding = AttachmentImgurBinding.inflate(LayoutInflater.from(parent.getContext()), null, false);

return new ImgurAttachmentViewHolder(attachmentImgurBinding, imgurAttachment);
}

private Attachment containsImgurAttachments(@NotNull Message message) {
for (int i = 0; i < message.getAttachments().size(); i++) {
String imageUrl = message.getAttachments().get(i).getImageUrl();

if (imageUrl != null && imageUrl.contains("imgur")) {
return message.getAttachments().get(i);
}
}

return null;
}

private static class ImgurAttachmentViewHolder extends InnerAttachmentViewHolder {

public ImgurAttachmentViewHolder(AttachmentImgurBinding binding,
@Nullable Attachment imgurAttachment) {
super(binding.getRoot());

ShapeAppearanceModel shapeAppearanceModel = binding.ivMediaThumb.getShapeAppearanceModel()
.toBuilder()
.setAllCornerSizes(binding.ivMediaThumb.getResources().getDimension(io.getstream.chat.android.ui.R.dimen.stream_ui_selected_attachment_corner_radius))
.build();

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);
}
}
}
}

This file was deleted.

Loading