Skip to content
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

[ECO-4943] feat: basic sending and receiving messages in the Chat #16

Merged
merged 3 commits into from
Sep 18, 2024

Conversation

ttypic
Copy link
Collaborator

@ttypic ttypic commented Sep 13, 2024

Resolves #2

In this PR, we introduce the basic implementation for sending and receiving messages. Corner cases and potential race conditions are not the focus at this stage and will be addressed later, once Room lifecycle management is implemented and a unified test suite is set up.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced message retrieval with optional parameters for more granular control.
    • Introduced new utility functions for JSON handling, improving robustness.
    • Simplified chat functionality by transitioning to ChatClient for message handling.
  • Improvements

    • Default parameters added to several functions for easier usage.
    • Improved encapsulation and resource management in room handling.
    • Enhanced test suite for messaging functionality, ensuring reliability.
  • Refactor

    • Streamlined test setup code for better readability and maintainability.
    • Refactored message subscription logic for improved user experience.
    • Introduced a new mechanism for deferred value handling in utility functions.
    • Updated internal structure of various classes to enhance maintainability.

Copy link

coderabbitai bot commented Sep 13, 2024

Walkthrough

The changes primarily involve refactoring and enhancing the chat functionality in the Ably library. Key updates include the introduction of a ChatClient, modifications to message retrieval and subscription methods, and the addition of utility functions for JSON handling. The structure of several classes has been adjusted for better encapsulation and usability, while new test cases have been created to ensure the reliability of these functionalities.

Changes

Files Change Summary
chat-android/src/main/java/com/ably/chat/ChatApi.kt Changed class visibility to internal, updated getMessages to include an optional parameter fromSerial, and removed several private utility functions.
chat-android/src/main/java/com/ably/chat/ChatClient.kt Modified ChatClient function to provide a default value for clientOptions, making it optional.
chat-android/src/main/java/com/ably/chat/JsonUtils.kt Introduced utility functions for JSON handling, enhancing type safety and error reporting.
chat-android/src/main/java/com/ably/chat/Messages.kt Updated MessagesSubscription interface to allow optional parameters for message retrieval, added DefaultMessagesSubscription, and improved message handling.
chat-android/src/main/java/com/ably/chat/Room.kt Restructured DefaultRoom to improve encapsulation of the messages property and added a release() method.
chat-android/src/main/java/com/ably/chat/RoomOptions.kt Changed properties to use singleton instances instead of instantiable classes for RoomReactionsOptions and OccupancyOptions.
chat-android/src/main/java/com/ably/chat/Rooms.kt Updated get function to provide a default RoomOptions, changed map type to MutableMap<String, DefaultRoom>, and improved resource management in release.
chat-android/src/main/java/com/ably/chat/Utils.kt Added DeferredValue<T> class for deferred value handling and improved null handling in ChatChannelOptions.
chat-android/src/test/java/com/ably/chat/ChatApiTest.kt Refactored test setup for clarity and maintainability, introducing helper functions for mocking API responses.
chat-android/src/test/java/com/ably/chat/MessagesTest.kt Introduced comprehensive unit tests for DefaultMessages functionality, covering message sending and subscription management.
chat-android/src/test/java/com/ably/chat/TestUtils.kt Added utility functions for mocking API responses, streamlining the testing process.
chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt Introduced utility functions for building channel state changes and real-time channels.
example/src/main/java/com/ably/chat/example/MainActivity.kt Transitioned from ChatApi to ChatClient, simplifying message handling and subscription lifecycle management.

Assessment against linked issues

Objective Addressed Explanation
Implement sending and receiving chat messages (ECO-4943)

Possibly related PRs

Poem

🐰 In the meadow, a chat does bloom,
With messages flying, dispelling the gloom.
A client now dances, swift and light,
Sending and receiving, oh what a sight!
With JSON helpers, the code is neat,
Hopping along, our chat's now complete! 🌼


Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between e6ead2d and 14b1cfc.

Files selected for processing (13)
  • chat-android/src/main/java/com/ably/chat/ChatApi.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/ChatClient.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/JsonUtils.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/Messages.kt (4 hunks)
  • chat-android/src/main/java/com/ably/chat/Room.kt (2 hunks)
  • chat-android/src/main/java/com/ably/chat/RoomOptions.kt (2 hunks)
  • chat-android/src/main/java/com/ably/chat/Rooms.kt (3 hunks)
  • chat-android/src/main/java/com/ably/chat/Utils.kt (1 hunks)
  • chat-android/src/test/java/com/ably/chat/ChatApiTest.kt (5 hunks)
  • chat-android/src/test/java/com/ably/chat/MessagesTest.kt (1 hunks)
  • chat-android/src/test/java/com/ably/chat/TestUtils.kt (1 hunks)
  • chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt (1 hunks)
  • example/src/main/java/com/ably/chat/example/MainActivity.kt (6 hunks)
Files skipped from review due to trivial changes (1)
  • chat-android/src/test/java/com/ably/chat/ChatApiTest.kt
Files skipped from review as they are similar to previous changes (12)
  • chat-android/src/main/java/com/ably/chat/ChatApi.kt
  • chat-android/src/main/java/com/ably/chat/ChatClient.kt
  • chat-android/src/main/java/com/ably/chat/JsonUtils.kt
  • chat-android/src/main/java/com/ably/chat/Messages.kt
  • chat-android/src/main/java/com/ably/chat/Room.kt
  • chat-android/src/main/java/com/ably/chat/RoomOptions.kt
  • chat-android/src/main/java/com/ably/chat/Rooms.kt
  • chat-android/src/main/java/com/ably/chat/Utils.kt
  • chat-android/src/test/java/com/ably/chat/MessagesTest.kt
  • chat-android/src/test/java/com/ably/chat/TestUtils.kt
  • chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt
  • example/src/main/java/com/ably/chat/example/MainActivity.kt

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai generate interesting stats about this repository and render them as a table.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.
Early access features: enabled

We are currently testing the following features in early access:

  • OpenAI o1 for code reviews: OpenAI's new o1 model is being tested for generating code suggestions in code reviews.

Note:

  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

It's proven to be easier to manage to implement when subscription return object that you can use to unsubscribe
@ttypic ttypic force-pushed the ECO-4943/sending-receiving-messages branch from 98158ac to 4d3c92c Compare September 13, 2024 16:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 14aecaf and 98158ac.

Files selected for processing (13)
  • chat-android/src/main/java/com/ably/chat/ChatApi.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/ChatClient.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/JsonUtils.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/Messages.kt (4 hunks)
  • chat-android/src/main/java/com/ably/chat/Room.kt (2 hunks)
  • chat-android/src/main/java/com/ably/chat/RoomOptions.kt (2 hunks)
  • chat-android/src/main/java/com/ably/chat/Rooms.kt (3 hunks)
  • chat-android/src/main/java/com/ably/chat/Utils.kt (1 hunks)
  • chat-android/src/test/java/com/ably/chat/ChatApiTest.kt (5 hunks)
  • chat-android/src/test/java/com/ably/chat/MessagesTest.kt (1 hunks)
  • chat-android/src/test/java/com/ably/chat/TestUtils.kt (1 hunks)
  • chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt (1 hunks)
  • example/src/main/java/com/ably/chat/example/MainActivity.kt (5 hunks)
Files skipped from review due to trivial changes (1)
  • chat-android/src/test/java/com/ably/chat/ChatApiTest.kt
Additional comments not posted (46)
chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt (2)

12-17: LGTM!

The buildChannelStateChange function provides a convenient way to create ChannelStateChange objects for testing purposes. The function parameters cover all the necessary fields to construct a ChannelStateChange object.

Note: This function can be removed if the visibility of the ChannelStateChange constructor is increased in the future.


23-28: LGTM!

The buildRealtimeChannel function provides a convenient way to create realtime Channel objects for testing purposes. The function initializes the client with a dummy API key and sets the auto-connect option to false, which is appropriate for testing scenarios.

The function allows for easy retrieval of a channel by name, facilitating the interaction with Ably's real-time messaging capabilities.

chat-android/src/main/java/com/ably/chat/ChatClient.kt (1)

40-41: LGTM!

The change to provide a default value for the clientOptions parameter in the ChatClient function signature is a good enhancement. It improves the usability of the function by making the clientOptions argument optional, allowing for simpler instantiation when default options suffice. The change is backward compatible and does not introduce any breaking changes.

chat-android/src/test/java/com/ably/chat/TestUtils.kt (4)

8-14: LGTM!

The function correctly mocks the AsyncHttpPaginatedResponse object using the MockK library. The implementation is accurate and serves its purpose for testing.


16-25: LGTM!

The function correctly mocks the API response for retrieving messages from a chat room. It intercepts the GET request and invokes the callback with a mocked response, which is suitable for testing purposes.


27-38: LGTM!

The function correctly mocks the API response for sending a message to a chat room. It intercepts the POST request and returns a mocked response, which is suitable for testing purposes.


40-51: LGTM!

The function correctly mocks the API response for retrieving occupancy information of a chat room. It intercepts the GET request to the occupancy endpoint and returns a mocked response, which is suitable for testing purposes.

chat-android/src/main/java/com/ably/chat/RoomOptions.kt (2)

24-24: LGTM!

The change in the reactions property initialization and the corresponding change in the RoomReactionsOptions class declaration to an object declaration are consistent. Treating RoomReactionsOptions as a singleton object is a valid approach.


30-30: LGTM!

The change in the occupancy property initialization and the corresponding change in the OccupancyOptions class declaration to an object declaration are consistent. Treating OccupancyOptions as a singleton object is a valid approach.

chat-android/src/main/java/com/ably/chat/Rooms.kt (4)

27-27: LGTM!

Providing a default value for the options parameter enhances the flexibility of the get function. It allows the method to be called without explicitly passing RoomOptions, making it easier to retrieve a room without needing to specify options every time.


50-50: LGTM!

Changing the type of the roomIdToRoom map from MutableMap<String, Room> to MutableMap<String, DefaultRoom> indicates a more specific implementation of the Room interface. This change aligns with the principle of using the most specific type possible and may provide additional functionality or optimizations specific to the default implementation.


75-75: LGTM!

Removing the room from the roomIdToRoom map in the release method ensures that the room is no longer accessible through the Rooms interface. This change is part of the proper resource management and cleanup process when a room is released.


76-76: LGTM!

Calling the release function of the room when it is removed from the roomIdToRoom map ensures proper resource management. It invokes the release logic on the room being removed, which may help prevent memory leaks or other resource-related issues. This change ensures that the room is properly cleaned up when it is no longer needed.

chat-android/src/main/java/com/ably/chat/Utils.kt (2)

42-45: LGTM!

The change handles potential null values for options.params more gracefully by using the Elvis operator to provide a default empty map. This is a good practice to avoid null pointer exceptions.


48-103: Excellent work!

The new DeferredValue<T> class is a well-designed and thread-safe implementation for deferring the evaluation of a value. The class is thoroughly documented, making its purpose and usage clear.

Key highlights:

  • The class is marked as internal, which is appropriate since it's not intended for public use.
  • The implementation is thread-safe, using a lock object to ensure thread safety during value assignment and observer notifications.
  • The completeWith method ensures that the value can only be set once, throwing an IllegalStateException if called again.
  • The await method allows a coroutine to wait until the value is completed, resuming with the completed value once available.

Overall, this is a solid addition to the codebase.

chat-android/src/main/java/com/ably/chat/JsonUtils.kt (9)

11-12: LGTM!

The function correctly converts a nullable JsonElement to an HttpCore.RequestBody using the appropriate utility function.


14-16: LGTM!

The function correctly converts a map of string key-value pairs into a JsonObject using the addProperty function.


18-20: LGTM!

The function correctly converts a JsonElement into a map of strings, ensuring that only JSON primitives are included.


22-29: LGTM!

The function correctly ensures that a JsonElement is a JsonObject and throws an AblyException with an appropriate error message when the expected JSON structure is not met.


31-42: LGTM!

The function correctly retrieves a string value from a JsonElement for a given member name, ensuring that the value is a JsonPrimitive and throwing an AblyException with an appropriate error message when the expected JSON structure is not met.


44-54: LGTM!

The function correctly retrieves a long value from a JsonElement for a given member name, ensuring that the value is a valid long and throwing an AblyException with an appropriate error message when the expected JSON structure is not met or the value is not a valid long.


56-66: LGTM!

The function correctly retrieves an int value from a JsonElement for a given member name, ensuring that the value is a valid int and throwing an AblyException with an appropriate error message when the expected JSON structure is not met or the value is not a valid int.


68-79: LGTM!

The function correctly retrieves a JsonPrimitive value from a JsonElement for a given member name, ensuring that the value is a JsonPrimitive and throwing an AblyException with an appropriate error message when the expected JSON structure is not met.


81-84: LGTM!

The function correctly retrieves a JsonElement value from a JsonElement for a given member name, ensuring that the field exists and throwing an AblyException with an appropriate error message when the required field is missing.

chat-android/src/main/java/com/ably/chat/Room.kt (3)

93-95: LGTM!

The introduction of a private backing property _messages is a good practice for encapsulation. The initialization of _messages with the required dependencies is correct.


99-100: LGTM!

Overriding the messages property to return the private _messages property is a good practice for encapsulation. This change is consistent with the introduction of the private _messages property.


135-138: LGTM!

The introduction of the release() function is a good practice for resource management. Calling the release() function on the private _messages property is correct. This change is consistent with the list of alterations provided in the AI-generated summary.

chat-android/src/main/java/com/ably/chat/ChatApi.kt (2)

17-17: LGTM!

Changing the class visibility to internal is a good practice to encapsulate the implementation details and restrict the usage of the class to the same module.


24-30: LGTM!

The addition of the fromSerial parameter provides more granular control over message retrieval, which enhances the flexibility of the message-fetching logic. The changes to the params variable construction based on the fromSerial parameter are implemented correctly.

example/src/main/java/com/ably/chat/example/MainActivity.kt (5)

17-17: LGTM!

The import statement is necessary and correctly added.


24-24: LGTM!

The import statement is necessary and correctly added.


Line range hint 35-42: LGTM!

The import statements are necessary and correctly added.


49-58: LGTM!

The realtimeClient and chatClient are correctly created with the necessary configurations.


Line range hint 75-134: LGTM!

The changes in the Chat composable function are well-implemented and align with the transition from ChatApi to ChatClient. The usage of DisposableEffect for managing the subscription lifecycle and retrieving previous messages is correct. The updates to the LazyColumn improve the user experience by automatically scrolling to the latest message. The message sending functionality is correctly updated to use room.messages.send.

Overall, the changes enhance the chat functionality and provide a better structure for message handling.

chat-android/src/test/java/com/ably/chat/MessagesTest.kt (5)

55-86: LGTM!

The test function correctly verifies the ability to send a message and ensures that the response matches the expected structure. The mocking of the API response and the assertion of the sent message against the expected Message object are implemented correctly.


88-140: LGTM!

The test function correctly verifies the ability to subscribe to incoming messages and handle the received messages. The mocking of the subscription, the simulation of an incoming message, and the assertion of the received message event against the expected MessageEvent object are implemented correctly.


142-153: LGTM!

The test function correctly verifies that accessing message history when not subscribed throws an AblyException with the expected error code. The subscription and immediate cancellation, followed by the assertion of the exception, are implemented correctly.


155-168: LGTM!

The test function correctly verifies that each subscription maintains its own channel serial. The setting of the channelSerial property, the creation of multiple subscriptions, and the assertions of the fromSerialProvider for each subscription are implemented correctly.


170-188: LGTM!

The test function correctly verifies that the subscription updates the channel serial after a reattach with resume = false. The setting of the channelSerial property, the creation of the subscription, the simulation of the channel state change, and the assertion of the updated fromSerialProvider are implemented correctly.

chat-android/src/main/java/com/ably/chat/Messages.kt (7)

182-183: LGTM!

The change to the getPreviousMessages method signature in the MessagesSubscription interface improves usability by allowing callers to specify message retrieval criteria directly. The default parameter values ensure backward compatibility.


186-205: Looks good!

The DefaultMessagesSubscription class correctly implements the updated MessagesSubscription interface. The use of fromSerialProvider to associate deferred channel serial values with the current channel's state helps manage message continuity and prevent duplication, aligning with the PR objective of improving message handling.


213-231: Great improvements!

The changes to the DefaultMessages class enhance the messaging functionality by providing better management of listeners and handling of channel state changes. The use of updateChannelSerialsAfterDiscontinuity ensures that listeners are updated correctly after any discontinuities, improving the robustness of message handling and aligning with the PR objectives.


234-272: Nicely done!

The updates to the subscribe method in the DefaultMessages class improve the robustness of message handling by parsing the message data and emitting it to listeners. The use of associateWithCurrentChannelSerial helps manage message continuity and prevent duplication, aligning with the PR objective of enhancing the chat functionality.


283-285: Good addition!

The release method is a useful addition for cleaning up resources when the DefaultMessages instance is no longer needed.


303-308: Helpful utility method!

The requireChannelSerial method is a helpful utility for retrieving the channel serial or throwing an exception if it's not available, improving code readability and error handling.


341-352: Nice utility types for parsing message data!

The addition of the PubSubMessageData data class and parsePubSubMessageData function improves code organization and readability by encapsulating the parsing logic for message data from the Pub/Sub channel. The parsePubSubMessageData function handles invalid data by throwing an exception, ensuring that only valid message data is processed. These changes align with the PR objective of enhancing the chat functionality and improving message handling.

Comments failed to post (1)
chat-android/src/main/java/com/ably/chat/Messages.kt (1)

293-301: > [!TIP]

Codebase Verification

Based on the analysis of the provided code snippets and the potential race condition, we can now generate a final response.

Race condition in associateWithCurrentChannelSerial confirmed, impacting getPreviousMessages

The race condition mentioned in the review comment is indeed present and can potentially lead to duplicated messages in getPreviousMessages calls. Here's a summary of the issue:

  1. The associateWithCurrentChannelSerial method sets the channel serial when the channel is attached or becomes attached.
  2. The getPreviousMessages method relies on the fromSerialProvider, which is set by associateWithCurrentChannelSerial.
  3. If the channel state changes between checking the state and setting the serial, it could lead to inconsistencies.

To address this issue, consider the following recommendations:

  • Implement a synchronization mechanism to ensure atomic operations when setting and reading the channel serial.
  • Use a thread-safe data structure or a concurrent primitive to store and update the channel serial.
  • Consider implementing a retry mechanism in getPreviousMessages to handle cases where the channel serial might be inconsistent.
  • Add logging or telemetry to track occurrences of this race condition in production.

These changes will help improve the reliability of the messaging system and reduce the likelihood of duplicated messages.

Analysis chain

Potential race condition in associateWithCurrentChannelSerial.

The associateWithCurrentChannelSerial method helps manage message continuity, but the comment indicates a potential race condition that could lead to duplicated messages in getPreviousMessages calls.

Please verify the impact of this race condition on message duplication and consider addressing it to ensure the reliability of the messaging system. You can use the following script to search for instances of getPreviousMessages and analyze how the race condition might affect its behavior:


To gather more information about the implementation and usage of getPreviousMessages and its relation to the potential race condition, let's examine the Messages.kt file more closely.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for `getPreviousMessages` usage and analyze the impact of the race condition.

# Test: Search for `getPreviousMessages` usage.
rg --type kotlin $'getPreviousMessages'

Length of output: 758


Script:

#!/bin/bash
# Description: Examine the implementation of getPreviousMessages and related methods in Messages.kt

# Search for the getPreviousMessages implementation and surrounding context
rg --type kotlin -A 20 -B 10 'suspend fun getPreviousMessages' chat-android/src/main/java/com/ably/chat/Messages.kt

# Search for usage of associateWithCurrentChannelSerial
rg --type kotlin 'associateWithCurrentChannelSerial' chat-android/src/main/java/com/ably/chat/Messages.kt

# Search for usage of channel serials in message retrieval
rg --type kotlin 'channelSerial' chat-android/src/main/java/com/ably/chat/Messages.kt

Length of output: 2466

@ttypic ttypic force-pushed the ECO-4943/sending-receiving-messages branch from 4d3c92c to 8d136b2 Compare September 13, 2024 16:51
.gitignore Outdated
@@ -96,6 +96,7 @@ publish.properties
/.idea/jarRepositories.xml
/.idea/misc.xml
/.idea/shelf
/.idea/uiDesigner.xml
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we just ignore all of .idea?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, IDE specific settings should be ignored .idea/**

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, we shouldn't ignore all files in .idea folder. It contains dictionary and codestyles settings that are useful if you use jetbrains IDE (Android Studio or IDEA). It's a good practice to add this settings for kotlin projects. But I obviously copied really bad gitignore pattern from some kotlin library template, didn't put much thought to it. I'll remove changes to gitignore in this PR and add separate PR that introduce updates to gitignore

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

channelStateListener = ChannelStateListener {
if (!it.resumed) updateChannelSerialsAfterDiscontinuity()
}
channel.on(ChannelState.attached, channelStateListener)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We also need to handle ChannelEvent.update here - which is RTL2g in the core feature spec

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, nice catch!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

val pubSubMessage = it ?: throw AblyException.fromErrorInfo(
ErrorInfo("Got empty pubsub channel message", HttpStatusCodes.BadRequest, ErrorCodes.BadRequest),
)
val data = parsePubSubMessageData(pubSubMessage.data)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Possible optimisation we can do here - rather than parsing data for every subscription, can have an "internal" subscriber that's put on the channel which then translates once and emits to all "actual" subscribers.

In JS we did this by registering the "internal" subscriber with the realtime channel, then registering user listeners with an EventEmitter instance on this class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking at code further down, won't this cause duplicate messages on multiple subscriptions?

As multiple instances of messageListener will call emitMessage which iterates all listeners?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, you right, I'll add tests :) I was thinking to use internal eventbus, but decided to do this later, because we need to decide on implementation details. I'll create an issue for this

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#18

emitMessage(chatMessage)
}

channel.subscribe(MessageEventType.Created.eventName, messageListener)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would cause an implicit channel attach which will break room lifecycle - subscribing should cause no side-effects.

I assume there's an ably-java ticket that needs doing first? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, of course, ably/ably-java#1027, I think until it release we can leave it as is. I'll add separate issue for this, too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#17

val data = parsePubSubMessageData(pubSubMessage.data)
val chatMessage = Message(
roomId = roomId,
createdAt = pubSubMessage.timestamp,
Copy link
Collaborator

Choose a reason for hiding this comment

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

In JS we've tended towards native date types for createdAt - not sure what's better for Kotlin?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't have strong opinion right now. Kotlin standard library doesn't have date type out of the box, and I still believe that one day we can provide Kotlin multiplatform support for all our products :) I'll think about this, look how easy to use timestamps in example app. Also want to research how libraries with kotlin multiplatform support like apollo-graphql deals with this. For now I suggest keeping date as Long

Copy link
Contributor

Choose a reason for hiding this comment

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

wdyt about https://github.com/Kotlin/kotlinx-datetime. It's a multiplatform datetime library

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If I’m not mistaken, it’s not stable yet, so I don’t think we should depend on it right now. We can decide this later—it’s not an architectural design decision that will cause issues for us later, and it will be easy to refactor whenever we want.

@ttypic ttypic force-pushed the ECO-4943/sending-receiving-messages branch 3 times, most recently from 9b4a5e8 to 60813de Compare September 17, 2024 09:45
@ttypic ttypic requested a review from AndyTWF September 17, 2024 09:47
Copy link
Contributor

@sacOO7 sacOO7 left a comment

Choose a reason for hiding this comment

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

Overall looks good. More tests can be added in the future.
( Not sure if we support sandbox integration tests yet )
Also, need to update example app with clean code as per comment #16 (comment)

@ttypic ttypic force-pushed the ECO-4943/sending-receiving-messages branch 2 times, most recently from 17cdb51 to e6ead2d Compare September 17, 2024 12:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 60813de and e6ead2d.

Files selected for processing (13)
  • chat-android/src/main/java/com/ably/chat/ChatApi.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/ChatClient.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/JsonUtils.kt (1 hunks)
  • chat-android/src/main/java/com/ably/chat/Messages.kt (4 hunks)
  • chat-android/src/main/java/com/ably/chat/Room.kt (2 hunks)
  • chat-android/src/main/java/com/ably/chat/RoomOptions.kt (2 hunks)
  • chat-android/src/main/java/com/ably/chat/Rooms.kt (3 hunks)
  • chat-android/src/main/java/com/ably/chat/Utils.kt (1 hunks)
  • chat-android/src/test/java/com/ably/chat/ChatApiTest.kt (5 hunks)
  • chat-android/src/test/java/com/ably/chat/MessagesTest.kt (1 hunks)
  • chat-android/src/test/java/com/ably/chat/TestUtils.kt (1 hunks)
  • chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt (1 hunks)
  • example/src/main/java/com/ably/chat/example/MainActivity.kt (6 hunks)
Files skipped from review due to trivial changes (1)
  • chat-android/src/test/java/com/ably/chat/ChatApiTest.kt
Files skipped from review as they are similar to previous changes (11)
  • chat-android/src/main/java/com/ably/chat/ChatApi.kt
  • chat-android/src/main/java/com/ably/chat/ChatClient.kt
  • chat-android/src/main/java/com/ably/chat/JsonUtils.kt
  • chat-android/src/main/java/com/ably/chat/Messages.kt
  • chat-android/src/main/java/com/ably/chat/Room.kt
  • chat-android/src/main/java/com/ably/chat/RoomOptions.kt
  • chat-android/src/main/java/com/ably/chat/Rooms.kt
  • chat-android/src/main/java/com/ably/chat/Utils.kt
  • chat-android/src/test/java/com/ably/chat/MessagesTest.kt
  • chat-android/src/test/java/com/ably/chat/TestUtils.kt
  • chat-android/src/test/java/io/ably/lib/realtime/RealtimeUtils.kt
Additional comments not posted (1)
example/src/main/java/com/ably/chat/example/MainActivity.kt (1)

190-219: Great use of @Preview annotations for UI components

Adding @Preview composables for MessageBubble and ChatInputField enhances the development experience by allowing real-time previews of these UI components. This addresses previous concerns about the lack of local previews and facilitates UI adjustments without the need for repeated app runs.

Comment on lines +86 to +103
DisposableEffect(Unit) {
val subscription = room.messages.subscribe {
messages += it.message
coroutineScope.launch {
messages = chatApi.getMessages(roomId, QueryOptions(orderBy = OldestFirst)).items
listState.animateScrollToItem(messages.size - 1)
}
}) {
Text("Load")
}

coroutineScope.launch {
messages = subscription.getPreviousMessages().items.reversed()
if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
}

onDispose {
subscription.unsubscribe()
}
}

Copy link

@coderabbitai coderabbitai bot Sep 17, 2024

Choose a reason for hiding this comment

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

Replace DisposableEffect with LaunchedEffect for coroutine management

Using DisposableEffect in this context may lead to coroutines that outlive the composable's lifecycle, potentially causing memory leaks. Instead, consider using LaunchedEffect, which automatically cancels coroutines when the composable leaves the composition. This ensures that resources are properly managed.

Apply this diff to refactor the code:

-DisposableEffect(Unit) {
-    val subscription = room.messages.subscribe {
-        messages += it.message
-        coroutineScope.launch {
-            listState.animateScrollToItem(messages.size - 1)
-        }
-    }
-
-    coroutineScope.launch {
-        messages = subscription.getPreviousMessages().items.reversed()
-        if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
-    }
-
-    onDispose {
-        subscription.unsubscribe()
-    }
-}
+LaunchedEffect(Unit) {
+    val subscription = room.messages.subscribe {
+        messages += it.message
+        listState.animateScrollToItem(messages.size - 1)
+    }
+
+    messages = subscription.getPreviousMessages().items.reversed()
+    if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
+
+    awaitDispose {
+        subscription.unsubscribe()
+    }
+}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DisposableEffect(Unit) {
val subscription = room.messages.subscribe {
messages += it.message
coroutineScope.launch {
messages = chatApi.getMessages(roomId, QueryOptions(orderBy = OldestFirst)).items
listState.animateScrollToItem(messages.size - 1)
}
}) {
Text("Load")
}
coroutineScope.launch {
messages = subscription.getPreviousMessages().items.reversed()
if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
}
onDispose {
subscription.unsubscribe()
}
}
LaunchedEffect(Unit) {
val subscription = room.messages.subscribe {
messages += it.message
listState.animateScrollToItem(messages.size - 1)
}
messages = subscription.getPreviousMessages().items.reversed()
if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
awaitDispose {
subscription.unsubscribe()
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There is no method awaitDispose in the CoroutineScope. coroutineScope in the code is produced by rememberCoroutineScope function, so it can't outlive the composable's lifecycle

Copy link

Choose a reason for hiding this comment

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

@ttypic, thank you for the clarification. You're absolutely right—awaitDispose isn't applicable here, and since coroutineScope from rememberCoroutineScope is tied to the composable's lifecycle, it won't outlive the composable. Using DisposableEffect as you've implemented is appropriate. I appreciate you pointing that out.

(。•́︿•̀。)


Learnings added
Learnt from: ttypic
PR: ably-labs/ably-chat-kotlin#16
File: example/src/main/java/com/ably/chat/example/MainActivity.kt:86-103
Timestamp: 2024-09-17T15:07:06.194Z
Learning: When using `rememberCoroutineScope`, the `CoroutineScope` is tied to the composable's lifecycle, so replacing `DisposableEffect` with `LaunchedEffect` is unnecessary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you make the face you made again 🙉

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

Choose a reason for hiding this comment

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

Nah it was for you @coderabbitai

Copy link

Choose a reason for hiding this comment

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

Sure thing @sacOO7!

(。•́︿•̀。)


Learnings used
Learnt from: ttypic
PR: ably-labs/ably-chat-kotlin#16
File: example/src/main/java/com/ably/chat/example/MainActivity.kt:86-103
Timestamp: 2024-09-17T15:07:06.349Z
Learning: When using `rememberCoroutineScope`, the `CoroutineScope` is tied to the composable's lifecycle, so replacing `DisposableEffect` with `LaunchedEffect` is unnecessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you describe what it exactly represents @coderabbitai

Copy link

Choose a reason for hiding this comment

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

@sacOO7, the face (。•́︿•̀。) represents a look of sadness or disappointment. It conveys feelings of being sorry or empathetic in response to the situation.


Learnings used
Learnt from: ttypic
PR: ably-labs/ably-chat-kotlin#16
File: example/src/main/java/com/ably/chat/example/MainActivity.kt:86-103
Timestamp: 2024-09-17T15:07:06.349Z
Learning: When using `rememberCoroutineScope`, the `CoroutineScope` is tied to the composable's lifecycle, so replacing `DisposableEffect` with `LaunchedEffect` is unnecessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add to the learnings to make happy face when you feel empathetic next time

In this commit, we introduce the basic implementation for sending and receiving messages. Corner cases and potential race conditions are not the focus at this stage and will be addressed later, once Room lifecycle management is implemented and a unified test suite is set up.
@ttypic ttypic force-pushed the ECO-4943/sending-receiving-messages branch from e6ead2d to 14b1cfc Compare September 17, 2024 14:38
@ttypic ttypic changed the base branch from feat-use-cancelation-object to main September 18, 2024 11:17
@ttypic ttypic merged commit 45deb6a into main Sep 18, 2024
2 checks passed
@ttypic ttypic deleted the ECO-4943/sending-receiving-messages branch September 18, 2024 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Implement sending and receiving chat messages
3 participants