Chat component SDK is a collection of modules that can be added to any project to simplify the development of basic chat functionality. It is an easily extendable solution that is built on top of Firebase and allows for easy UI customization.
- Multiple backend support
- Cloud Storage support
- Kotlin-first
- API Level 21+ (compatible with 85% of Android devices)
- Hook up to the selected backend support
- Ready-to-use and customizable UI
- Various types of messages
- Text message
- Image message (from camera or gallery)
- Notification about the state of an image upload
- Tap to show the timestamp of the sent message
Add Jitpack repository in your root build.gradle
at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
implementation "com.github.strvcom.android-research-chat-component:core:1.0.0-beta01"
You should use the core module if you want to have custom backend implementation. In this case, you will be forced to implement all the required interfaces on your own.
implementation "com.github.strvcom.android-research-chat-component:firestore:1.0.0-beta01"
You should use firestore module if you want to use the default Cloud Firestore implementation for your backend.
The database structure required for using the default Firestore implementation is described in Firestore docs.
There are two convenient extension methods for an Activity in activity.kt available to simplify work with opening a camera and gallery in case only the basic functionality is required:
Activity.openCamera(imageUri: Uri)
Activity.openGalleryPhotoPicker(title: String)
In order to run the sample app, you need to add your own
google-services.json
file to the app-level directory of the app
module.
implementation "com.github.strvcom.android-research-chat-component:storage:1.0.0-beta01"
You should use storage module if you want to use the default Firebase Cloud implementation as your media storage.
Use ChatComponent.init()
function to configure and initialize the Chat
component SDK in your application.
class App : Application() {
override fun onCreate() {
super.onCreate()
// initialize Chat component
ChatComponent.init(
//application context
this,
//configuration
Configuration(
//ChatClient - provides interactions with the message source of data
chatClient,
//ConversationClient - provides interactions with the conversation source of data
conversationClient,
//MemberClient - provides information about the current user and other members
memberClient,
//MediaClient - provides interaction with a storage service
mediaClient,
//notification configuration - for a complete list of available attributes see Notification configuration in Send widget section
serviceConfig(CHANNEL_ID)
)
)
}
}
Task
API is widely used in Chat component SDK to represent a promise
that computation will be done. It is a wrapper around a result of
an asynchronous call.
There are three kinds of Task:
Task<Result, Error>
- Represents a single result of an asynchronous call that returns
<Result>
type in case of success and<Error>
in case of error.
- Represents a single result of an asynchronous call that returns
ProgressTask<Result, Error>
- Represents a single result of an asynchronous call and
periodically notifies about progress of the call. It returns
<Result>
type in case of success and<Error>
in case of error.
- Represents a single result of an asynchronous call and
periodically notifies about progress of the call. It returns
ObservableTask<Result, Error>
- Represents a stream with real time updates of the result of type
<Result>
. Returns<Error>
when an error occurs.
- Represents a stream with real time updates of the result of type
To be notified when the task succeeds, call onSuccess
:
task.onSuccess { result ->
Log.d("TAG", "Task completed successfully with a result: $result")
}
To be notified when the task fails, call onError
:
task.onError { error ->
Log.e("TAG", "Task failed with an exception: ${error.localizedMessage ?: "Unknown error"}")
}
To be notified when the task succeeds, call onSuccess
:
progressTask.onSuccess { result ->
Log.d("TAG", "Task completed successfully with a result: $result")
}
To be notified when the task fails, call onError
:
progressTask.onError { error ->
Log.e("TAG", "Task failed with an exception: ${error.localizedMessage ?: "Unknown error"}")
}
To be repeatedly notified about the progress, call onProgress
:
progressTask.onProgress { progress ->
Log.d("TAG", "Task progress is: $progress")
}
To subscribe to the source of data, call onNext
"
observableTask.onNext { result ->
Log.d("TAG", "Task has a new result: $result")
}
To be notified when the task fails, call onError
:
observableTask.onError { error ->
Log.e("TAG", "Task failed with an exception: ${error.localizedMessage ?: "Unknown error"}")
}
You can convert any existing callback-based API to the Task API via
task
observableTask
progressTask
builder functions.
In this function, both invokeSuccess
and invokeError
can be used to
complete the task with a success or an error state. Repeated invocation
of any completing function is ignored.
interface Callback<T> {
fun onSuccess(result: T)
fun onError(e: Exception)
}
fun <T> taskCallback(block: (Callback<T>) -> Unit): Task<T, Throwable> = task<T, Throwable> {
block(object : Callback<T> {
override fun onSuccess(result: T) {
invokeSuccess(result)
}
override fun onError(e: Exception) {
invokeError(e)
}
})
}
In this function, invokeNext
can be used to emit a value to the stream
behind. invokeError
completes the task with an error state. Repeated
invocation of invokeError
function is ignored.
fun subscribe(): ObservableTask<List<Entity>, Throwable> =
observableTask<List<Entity>, Throwable>(::unsubscribe) {
listenerRegistration =
queryAource.addSnapshotListener { result, exception ->
if (exception != null) {
invokeError(exception)
} else {
val list = arrayListOf<Entity>()
result?.mapTo(list) { snapShot ->
snapShot.toObject(clazz).also { item ->
item.id = snapShot.id
}
}
invokeNext(list)
}
}
}
In this function, invokeProgress
can be used to notify about the
progress of the asynchronous call. invokeSuccess
and invokeError
complete the task with a success or an error state. Repeated invocation
of any completing function is ignored.
fun uploadImage(bitmap: Bitmap, uploadUrl: String, contentType: String) =
progressTask<DownloadUrl, Throwable> {
val metadata = storageMetadata {
this.contentType = contentType
}
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos)
val imagePath = path(firebaseStorage, uploadUrl)
imagePath.putBytes(bos.toByteArray(), metadata)
.addOnSuccessListener() {
logD("File was uploaded")
}.addOnFailureListener {
invokeError(it)
}.addOnProgressListener { snapshot ->
invokeProgress((100.0 * snapshot.bytesTransferred / snapshot.totalByteCount).toInt())
}.continueWithTask {
imagePath.downloadUrl
}.addOnSuccessListener { uri ->
invokeSuccess(uri)
}.addOnFailureListener {
invokeError(it)
}
}
Task operators were created to provide users with the option to smoothly transform task data. You can find the list of the supported operators here.
See the LICENSE file for license rights and limitations (APLv2).