Make stream_chat_persistence a add on dependency instead of shipping it with the llc

Signed-off-by: Sahil Kumar <xdsahil@gmail.com>
This commit is contained in:
Sahil Kumar
2021-01-27 12:52:37 +05:30
parent f48b7c8882
commit 1521558304
16 changed files with 128 additions and 189 deletions
+10 -8
View File
@@ -419,7 +419,7 @@ class Channel {
}
}
await _client.chatPersistence?.deleteMessageById(messageId);
await _client.chatPersistenceClient?.deleteMessageById(messageId);
}
return res;
@@ -487,7 +487,7 @@ class Channel {
PaginationParams options, {
bool preferOffline = false,
}) async {
final cachedReplies = await _client.chatPersistence?.getReplies(
final cachedReplies = await _client.chatPersistenceClient?.getReplies(
parentId,
lessThan: options?.lessThan,
);
@@ -603,7 +603,8 @@ class Channel {
}
if (preferOffline && cid != null) {
final updatedState = await _client.chatPersistence?.getChannelStateByCid(
final updatedState =
await _client.chatPersistenceClient?.getChannelStateByCid(
cid,
messagePagination: messagesPagination,
);
@@ -730,7 +731,7 @@ class Channel {
if (clearHistory == true) {
state.truncate();
await _client.chatPersistence?.deleteMessageByCid(_cid);
await _client.chatPersistenceClient?.deleteMessageByCid(_cid);
}
return _client.decode(response.data, EmptyResponse.fromJson);
@@ -869,7 +870,7 @@ class ChannelClientState {
_computeInitialUnread();
_channel._client.chatPersistence
_channel._client.chatPersistenceClient
?.getChannelThreads(_channel.cid)
?.then((threads) {
_threads = threads;
@@ -947,7 +948,8 @@ class ChannelClientState {
.on(EventType.channelTruncated, EventType.notificationChannelTruncated)
.listen((event) async {
final channel = event.channel;
await _channel._client.chatPersistence?.deleteMessageByCid(channel.cid);
await _channel._client.chatPersistenceClient
?.deleteMessageByCid(channel.cid);
truncate();
}));
}
@@ -1401,7 +1403,7 @@ class ChannelClientState {
set _channelState(ChannelState v) {
_channelStateController.add(v);
_channel._client.chatPersistence?.updateChannelState(v);
_channel._client.chatPersistenceClient?.updateChannelState(v);
}
/// The channel threads related to this channel
@@ -1414,7 +1416,7 @@ class ChannelClientState {
BehaviorSubject.seeded({});
set _threads(Map<String, List<Message>> v) {
_channel._client.chatPersistence?.updateMessages(
_channel._client.chatPersistenceClient?.updateMessages(
_channel.cid,
v.values.expand((v) => v).toList(),
);
+24 -31
View File
@@ -6,7 +6,6 @@ import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stream_chat/src/api/retry_policy.dart';
import 'package:stream_chat/src/event_type.dart';
@@ -21,7 +20,7 @@ import 'api/connection_status.dart';
import 'api/requests.dart';
import 'api/responses.dart';
import 'api/websocket.dart';
import 'db/stream_chat_persistence.dart';
import 'db/chat_persistence_client.dart';
import 'exceptions.dart';
import 'models/event.dart';
import 'models/message.dart';
@@ -87,7 +86,6 @@ class Client {
this.baseURL = _defaultBaseURL,
this.logLevel = Level.WARNING,
this.logHandlerFunction,
this.persistenceEnabled = true,
Duration connectTimeout = const Duration(seconds: 6),
Duration receiveTimeout = const Duration(seconds: 6),
Dio httpClient,
@@ -111,10 +109,11 @@ class Client {
logger.info('instantiating new client');
}
StreamChatPersistence _chatPersistence;
/// Client chat persistence client
ChatPersistenceClient chatPersistenceClient;
/// If true chat data will persist on disk
final bool persistenceEnabled;
/// Whether the chat persistence is available or not
bool get persistenceEnabled => chatPersistenceClient != null;
RetryPolicy _retryPolicy;
@@ -131,9 +130,6 @@ class Client {
/// The amount of time that will pass before disconnecting the client in the background
final Duration backgroundKeepAlive;
/// Client offline database
StreamChatPersistence get chatPersistence => _chatPersistence;
/// This client state
ClientState state;
@@ -345,7 +341,7 @@ class Client {
/// Call this function to dispose the client
void dispose() async {
await _chatPersistence?.disconnect();
await chatPersistenceClient?.disconnect();
await _disconnect();
httpClient.close();
await _controller.close();
@@ -427,8 +423,8 @@ class Client {
if (!event.isLocal) {
if (_synced && event.createdAt != null) {
await _chatPersistence?.updateConnectionInfo(event);
await _chatPersistence?.updateLastSyncAt(event.createdAt);
await chatPersistenceClient?.updateConnectionInfo(event);
await chatPersistenceClient?.updateLastSyncAt(event.createdAt);
}
}
@@ -459,12 +455,8 @@ class Client {
wsConnectionStatus.value = ConnectionStatus.connecting;
if (persistenceEnabled && _chatPersistence == null) {
_chatPersistence = StreamChatPersistenceImpl(
state.user.id,
logger: _detachedLogger('💽'),
);
await _chatPersistence.connect();
if (persistenceEnabled) {
await chatPersistenceClient.connect('db_${state.user.id}');
}
_ws = WebSocket(
@@ -514,10 +506,10 @@ class Client {
_ws.connectionStatus.addListener(_connectionStatusListener);
var event = await _chatPersistence?.getConnectionInfo();
var event = await chatPersistenceClient?.getConnectionInfo();
await _ws.connect().then((e) async {
await _chatPersistence?.updateConnectionInfo(e);
await chatPersistenceClient?.updateConnectionInfo(e);
event = e;
await resync();
}).catchError((err, stacktrace) {
@@ -532,14 +524,14 @@ class Client {
/// Get the events missed while offline to sync the offline storage
Future<void> resync([List<String> cids]) async {
final lastSyncAt = await chatPersistence?.getLastSyncAt();
final lastSyncAt = await chatPersistenceClient?.getLastSyncAt();
if (lastSyncAt == null) {
_synced = true;
return;
}
cids ??= await chatPersistence?.getChannelCids();
cids ??= await chatPersistenceClient?.getChannelCids();
if (cids?.isEmpty == true) {
return;
@@ -568,7 +560,7 @@ class Client {
handleEvent(event);
});
await _chatPersistence?.updateLastSyncAt(DateTime.now());
await chatPersistenceClient?.updateLastSyncAt(DateTime.now());
_synced = true;
} catch (error) {
logger.severe('Error during resync $error');
@@ -714,7 +706,7 @@ class Client {
channels.add(channel);
} else {
final newChannel = Channel.fromState(this, channelState);
await _chatPersistence
await chatPersistenceClient
?.updateChannelState(newChannel.state.channelState);
newChannel.state?.updateChannelState(channelState);
newChannels[newChannel.cid] = newChannel;
@@ -725,7 +717,7 @@ class Client {
state.channels = newChannels;
await _chatPersistence?.updateChannelQueries(
await chatPersistenceClient?.updateChannelQueries(
filter,
res.channels.map((c) => c.channel.cid).toList(),
paginationParams?.offset == null || paginationParams.offset == 0,
@@ -760,7 +752,7 @@ class Client {
@required List<SortOption> sort,
PaginationParams paginationParams = const PaginationParams(limit: 10),
}) async {
final offlineChannels = await _chatPersistence?.getChannelStates(
final offlineChannels = await chatPersistenceClient?.getChannelStates(
filter: filter,
sort: sort,
paginationParams: paginationParams,
@@ -775,7 +767,8 @@ class Client {
return channel;
} else {
final newChannel = Channel.fromState(this, channelState);
_chatPersistence?.updateChannelState(newChannel.state.channelState);
chatPersistenceClient
?.updateChannelState(newChannel.state.channelState);
newChannels[newChannel.cid] = newChannel;
return newChannel;
}
@@ -938,8 +931,8 @@ class Client {
logger.info(
'Disconnecting flushOfflineStorage: $flushChatPersistence; clearUser: $clearUser');
await _chatPersistence?.disconnect(flush: flushChatPersistence);
_chatPersistence = null;
await chatPersistenceClient?.disconnect(flush: flushChatPersistence);
chatPersistenceClient = null;
if (clearUser == true) {
state.dispose();
@@ -1328,7 +1321,7 @@ class ClientState {
void _listenChannelHidden() {
_subscriptions.add(_client.on(EventType.channelHidden).listen((event) {
_client._chatPersistence?.deleteChannels([event.cid]);
_client.chatPersistenceClient?.deleteChannels([event.cid]);
if (channels != null) {
channels = channels..removeWhere((cid, ch) => cid == event.cid);
}
@@ -1353,7 +1346,7 @@ class ClientState {
)
.listen((Event event) async {
final eventChannel = event.channel;
await _client._chatPersistence?.deleteChannels([eventChannel.cid]);
await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]);
if (channels != null) {
channels = channels..remove(eventChannel.cid);
}
@@ -9,15 +9,12 @@ import 'package:stream_chat/src/models/read.dart';
import 'package:stream_chat/src/models/user.dart';
///
abstract class StreamChatPersistence {
/// Creates a new connection to the database
Future<void> connect({
bool connectBackground = false,
bool logStatements = false,
});
abstract class ChatPersistenceClient {
/// Creates a new connection to the client
Future<void> connect(String name);
/// Closes the database instance
/// If [flush] is true, the database data will be deleted
/// Closes the client connection
/// If [flush] is true, the data will also be deleted
Future<void> disconnect({bool flush = false});
/// Get stored replies by messageId
+14 -40
View File
@@ -1,12 +1,11 @@
import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stream_chat/src/api/responses.dart';
import 'package:stream_chat/src/models/channel_model.dart';
import 'package:stream_chat/src/models/channel_state.dart';
import 'package:stream_chat/src/models/message.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
import 'package:meta/meta.dart';
import 'client.dart';
import 'models/own_user.dart';
@@ -20,21 +19,13 @@ class NotificationService {
) async {
if (message != null && client.persistenceEnabled) {
if (client?.state?.channels == null) {
final sharedPreferences = await _getSharedPreferences();
final userId = sharedPreferences.getString(KEY_USER_ID);
final chatPersistence = StreamChatPersistenceImpl(
userId,
logger: Logger('💽'),
);
await chatPersistence.connect();
await chatPersistence.updateChannelState(
final chatPersistenceClient = client.chatPersistenceClient;
await chatPersistenceClient.updateChannelState(
ChannelState(
channel: channelModel,
messages: [message],
),
);
await chatPersistence.disconnect();
} else {
final channel = client.state.channels[channelModel.cid];
channel.state.updateChannelState(
@@ -56,14 +47,11 @@ class NotificationService {
/// Gets the message using the client without storing it in the offline storage
/// It returns an object containing the information about the message and the channel
static Future<GetMessageResponse> getMessage(String messageId) async {
static Future<GetMessageResponse> _getMessage(String messageId) async {
final sharedPreferences = await _getSharedPreferences();
final apiKey = sharedPreferences.getString(KEY_API_KEY);
final client = Client(
apiKey,
persistenceEnabled: false,
);
final client = Client(apiKey);
final userId = sharedPreferences.getString(KEY_USER_ID);
final token = sharedPreferences.getString(KEY_TOKEN);
@@ -74,30 +62,16 @@ class NotificationService {
return res;
}
/// Stores the message in the offline storage
static Future<void> storeMessage(GetMessageResponse messageResponse) async {
final sharedPreferences = await _getSharedPreferences();
final userId = sharedPreferences.getString(KEY_USER_ID);
final chatPersistence = StreamChatPersistenceImpl(
userId,
logger: Logger('💽'),
);
await chatPersistence.connect();
await chatPersistence.updateChannelState(ChannelState(
messages: [messageResponse.message],
channel: messageResponse.channel,
));
await chatPersistence.disconnect();
}
/// Gets the message using the client and stores it in the offline storage
/// Gets the message using the client and calls a [storeMessageHandler] callback
/// with the message if the user wants to save the message in db
///
/// It returns an object containing the information about the message and the channel
static Future<GetMessageResponse> getAndStoreMessage(String messageId) async {
final getMessageResponse = await getMessage(messageId);
await storeMessage(getMessageResponse);
static Future<GetMessageResponse> getAndStoreMessage({
@required String messageId,
@required Future<void> Function(GetMessageResponse) storeMessageHandler,
}) async {
final getMessageResponse = await _getMessage(messageId);
await storeMessageHandler(getMessageResponse);
return getMessageResponse;
}
+1 -1
View File
@@ -27,4 +27,4 @@ export './src/models/reaction.dart';
export './src/models/read.dart';
export './src/models/user.dart';
export './src/notifications.dart';
export './src/db/stream_chat_persistence.dart';
export './src/db/chat_persistence_client.dart';
@@ -8,9 +8,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'choose_user_page.dart';
import 'main.dart';
import 'notifications_service.dart';
class AdvancedOptionsPage extends StatefulWidget {
@@ -278,8 +280,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
showLocalNotification: (!kIsWeb && Platform.isAndroid)
? showLocalNotification
: null,
persistenceEnabled: true,
);
)..chatPersistenceClient = chatPersistentClient;
try {
await client.setUser(
@@ -320,72 +321,13 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
return;
}
if (!kIsWeb) {
initNotifications(client);
}
Navigator.pop(context);
Navigator.pop(context);
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return FutureBuilder<StreamingSharedPreferences>(
future: StreamingSharedPreferences.instance,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SizedBox();
}
return PreferenceBuilder<int>(
preference: snapshot.data.getInt(
'theme',
defaultValue: 0,
),
builder: (context, snapshot) => MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
child: Builder(
builder: (context) =>
AnnotatedRegion<
SystemUiOverlayStyle>(
child: child,
value: SystemUiOverlayStyle(
systemNavigationBarColor:
StreamChatTheme.of(context)
.colorTheme
.white,
systemNavigationBarIconBrightness:
Theme.of(context)
.brightness ==
Brightness.dark
? Brightness.light
: Brightness.dark,
),
),
),
);
},
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: {
-1: ThemeMode.dark,
0: ThemeMode.system,
1: ThemeMode.light,
}[snapshot],
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: client.state.user == null
? Routes.CHOOSE_USER
: Routes.HOME,
),
);
},
);
},
),
);
loading = false;
await Navigator.pushReplacementNamed(
context,
Routes.APP,
arguments: client,
);
}
},
),
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'notifications_service.dart';
@@ -17,6 +18,11 @@ import 'routes/app_routes.dart';
import 'routes/routes.dart';
import 'search_text_field.dart';
final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.INFO,
connectionMode: ConnectionMode.background,
);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final secureStorage = FlutterSecureStorage();
@@ -29,8 +35,7 @@ void main() async {
logLevel: Level.INFO,
showLocalNotification:
(!kIsWeb && Platform.isAndroid) ? showLocalNotification : null,
persistenceEnabled: true,
);
)..chatPersistenceClient = chatPersistentClient;
if (userId != null) {
final token = await secureStorage.read(key: kStreamToken);
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:example/main.dart';
import 'package:flutter_apns/flutter_apns.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
hide Message;
@@ -36,8 +37,17 @@ Future backgroundHandler(Map<String, dynamic> notification) async {
print('new notification ${notification}');
final messageId = notification['data']['id'];
final notificationData =
await NotificationService.getAndStoreMessage(messageId);
final notificationData = await NotificationService.getAndStoreMessage(
messageId: messageId,
storeMessageHandler: (messageResponse) {
return chatPersistentClient.updateChannelState(
ChannelState(
messages: [messageResponse.message],
channel: messageResponse.channel,
),
);
},
);
showLocalNotification(
notificationData.message,
@@ -15,6 +15,12 @@ class AppRoutes {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case Routes.APP:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.APP),
builder: (_) {
return MyApp(args);
});
case Routes.HOME:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.HOME),
@@ -1,5 +1,6 @@
/// Define all the route names here
class Routes {
static const String APP = '/app';
static const String HOME = '/home';
static const String CHOOSE_USER = '/choose_user';
static const String ADVANCED_OPTIONS = '/advance_options';
@@ -9,8 +9,10 @@ environment:
dependencies:
flutter:
sdk: flutter
stream_chat_flutter:
stream_chat_flutter:
path: ../
stream_chat_persistence:
path: ../../stream_chat_persistence
flutter_apns: ^1.4.1
flutter_local_notifications: ^2.0.2
flutter_svg: ^0.19.1
@@ -11,7 +11,7 @@ class SharedDB {
static constructOfflineStorage(
String dbName, {
logStatements = false,
bool logStatements = false,
}) {
throw 'Unsupported Platform';
}
@@ -1,5 +1,4 @@
import 'package:stream_chat/stream_chat.dart';
import 'user_mapper.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
///
@@ -3,18 +3,30 @@ import 'package:stream_chat/stream_chat.dart';
import 'db/moor_chat_database.dart';
import 'db/shared/shared_db.dart';
///
class StreamChatPersistenceImpl extends StreamChatPersistence {
///
StreamChatPersistenceImpl(
this._userId, {
Logger logger,
}) : _logger = logger,
assert(_userId != null);
/// Various connection modes on which [StreamChatPersistenceClient] can work
enum ConnectionMode {
/// Connects the [StreamChatPersistenceClient] on a regular/default isolate
regular,
/// Connects the [StreamChatPersistenceClient] on a background isolate
background,
}
///
class StreamChatPersistenceClient extends ChatPersistenceClient {
///
StreamChatPersistenceClient({
/// Connection mode on which the client will work
ConnectionMode connectionMode = ConnectionMode.regular,
Level logLevel = Level.WARNING,
}) : assert(connectionMode != null),
assert(logLevel != null),
_connectionMode = connectionMode,
_logger = Logger.detached('💽')..level = logLevel;
final String _userId;
final Logger _logger;
MoorChatDatabase _db;
final Logger _logger;
final ConnectionMode _connectionMode;
bool get _debugAssertConnected {
assert(() {
@@ -30,27 +42,22 @@ class StreamChatPersistenceImpl extends StreamChatPersistence {
}
@override
Future<void> connect({
bool connectBackground = false,
bool logStatements = false,
}) async {
Future<void> connect(String name) async {
if (_db != null) {
throw Exception(
'An instance of StreamChatDatabase is already connected.\n'
'disconnect the previous instance before connecting again.',
);
}
final dbName = 'db_$_userId';
if (connectBackground) {
_logger?.info('Connecting on background isolate');
_db = await SharedDB.constructOfflineStorage(
dbName,
logStatements: logStatements,
);
} else {
_logger?.info('Connecting on a regular isolate');
_db = MoorChatDatabase(dbName, logStatements: logStatements);
switch (_connectionMode) {
case ConnectionMode.regular:
_logger.info('Connecting on a regular isolate');
_db = MoorChatDatabase(name);
return;
case ConnectionMode.background:
_logger.info('Connecting on background isolate');
_db = await SharedDB.constructOfflineStorage(name);
return;
}
}
@@ -202,9 +209,9 @@ class StreamChatPersistenceImpl extends StreamChatPersistence {
@override
Future<void> disconnect({bool flush = false}) async {
if (_db != null) {
_logger?.info('Disconnecting');
_logger.info('Disconnecting');
if (flush) {
_logger?.info('Flushing');
_logger.info('Flushing');
await _db.batch((batch) {
_db.allTables.forEach((table) {
_db.delete(table).go();
@@ -1,3 +1,3 @@
library stream_chat_persistence;
export 'src/stream_chat_persistence_impl.dart';
export 'src/stream_chat_persistence_client.dart';
@@ -11,6 +11,7 @@ dependencies:
moor: ^3.4.0
path: ^1.7.0
path_provider: ^1.6.27
sqlite3_flutter_libs: ^0.3.0
stream_chat:
path: ../dart_client