Skip to content

Convert to pure Dart package, fix socket lifecycle bugs, upgrade deps#32

Open
Korpyc wants to merge 16 commits intoknocklabs:mainfrom
Korpyc:main
Open

Convert to pure Dart package, fix socket lifecycle bugs, upgrade deps#32
Korpyc wants to merge 16 commits intoknocklabs:mainfrom
Korpyc:main

Conversation

@Korpyc
Copy link

@Korpyc Korpyc commented Mar 4, 2026

I've been waiting for almost two years, but no-one is fixing official package, so, I've made, hope you would like it. Because without those changes can't be used for production.
Yes, there are breaking changes, but as a result of those changes it's now work stable and reliable when you want re-init feed with different options(to show user only unread messages vs all in general tab or switch to another tenant id).

Summary

  • Remove Pigeon/native plugin code (Android/iOS), convert to pure Dart package.
    Push token retrieval is now the consumer's responsibility via firebase_messaging or similar.
  • Upgrade dependencies: freezed v3, phoenix_socket ^0.8.0, json_serializable ^6.13, SDK >=3.8.0
  • Rename API types with Knock prefix (ApiClient -> KnockApiClient, ApiResponse -> KnockApiResponse,
    ApiError -> KnockApiException) to avoid naming collisions
  • Fix socket getter not caching the PhoenixSocket instance (every access created a new connection)
  • Fix FeedClient _eventController.close() in wrong lifecycle hook causing zombie state
  • Add NetworkStatus.initial to distinguish pre-fetch state from ready
  • Add initial HTTP fetch in onListen so data loads without relying on BehaviorSubject replay
  • Fix socket disposal: close() before dispose() for clean WebSocket disconnect, close _status stream
  • Add explicit FeedClient.dispose() for deterministic cleanup
  • Fix channel join assertion crash on re-subscribe by synchronously removing channel from cache
  • Remove duplicate User tests from channel_test.dart

Korpyc added 14 commits March 3, 2026 20:50
…package

The SDK no longer fetches push notification tokens internally. Users
should use firebase_messaging (or their preferred push provider) to
obtain FCM/APNS tokens and pass them directly to registerTokenForChannel.
Bump freezed to v3, freezed_annotation to v3, phoenix_socket to v0.8,
json_serializable to v6.13, and update SDK constraints.
Regenerate all freezed, json_serializable, and mockito files.
…ck prefix

Rename ApiResponse to KnockApiResponse, ApiClient to KnockApiClient,
and ApiClientStatus to KnockApiClientStatus across all source and test files.
Remove ApiRequestBuilder typedef in favor of inline type.
Regenerate freezed and mockito files.
The socket getter used `??` instead of `??=`, so the built PhoenixSocket
was never stored in `_socket`. This caused every access to create a new
WebSocket connection, leaking sockets. It also meant `dispose()` could
never clean up the socket since `_socket` was always null.
The "Channel deserializes", "User serializes", and "User mutations" test
groups were copy-pasted from user_test.dart and tested User — not channels.
These 7 duplicate tests are already covered in user_test.dart.
Move _eventController.close() from onCancel to the API disposal handler
so the event stream lifecycle is tied to the API client, not the feed
stream subscription. This prevents a zombie state where FeedClient
methods fire HTTP requests but silently lose events after unsubscribe.

Add isClosed guards on all _eventController.add() calls as a safety net
for the async race in _fetch() — an HTTP response can arrive after the
API client is disposed.
…eady

Feed.initialState() now uses NetworkStatus.initial instead of ready,
allowing consumers to differentiate between "haven't fetched yet" (show
skeleton/placeholder) and "fetched successfully" (show content).

The JSON default remains NetworkStatus.ready since deserialized API
responses have already been fetched. Generated files need regeneration
via build_runner.
- Close connected socket before dispose to send proper WebSocket close frame
- Close _status StreamController to prevent resource leak
- Add explicit FeedClient.dispose() for deterministic cleanup
- Add defensive cancel before re-subscribing to channel messages
- Guard _onNewMessageReceived against post-disposal processing
leave() is async (waits for server ack before removing from cache),
so quick unsubscribe/re-subscribe finds the stale cached channel
with _joinedOnce = true. Adding immediate removeChannel() after
leave() ensures the cache is cleared synchronously while still
sending phx_leave to the server.
@Korpyc Korpyc requested a review from a team as a code owner March 4, 2026 13:29
@Korpyc Korpyc requested review from MikeCarbone and meryldakin and removed request for a team March 4, 2026 13:29
Korpyc added 2 commits March 4, 2026 15:32
The previous version (^2.1.0) used deprecated Android Gradle Plugin
APIs incompatible with Java 17. Upgraded to ^5.0.1 which supports
modern AGP and Java 17. Updated timezone access to use the new
TZDateTime.identifier API.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant