Make friend api calls more efficient and increase update rate
This commit is contained in:
parent
6dd3fa813f
commit
ece488d177
7 changed files with 65 additions and 31 deletions
|
@ -1,12 +1,11 @@
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||||
import 'package:contacts_plus_plus/models/friend.dart';
|
import 'package:contacts_plus_plus/models/friend.dart';
|
||||||
|
|
||||||
class FriendApi {
|
class FriendApi {
|
||||||
static Future<List<Friend>> getFriendsList(ApiClient client) async {
|
static Future<List<Friend>> getFriendsList(ApiClient client, {DateTime? lastStatusUpdate}) async {
|
||||||
final response = await client.get("/users/${client.userId}/friends");
|
final response = await client.get("/users/${client.userId}/friends${lastStatusUpdate != null ? "?lastStatusUpdate=${lastStatusUpdate.toUtc().toIso8601String()}" : ""}");
|
||||||
ApiClient.checkResponse(response);
|
ApiClient.checkResponse(response);
|
||||||
final data = jsonDecode(response.body) as List;
|
final data = jsonDecode(response.body) as List;
|
||||||
return data.map((e) => Friend.fromMap(e)).toList();
|
return data.map((e) => Friend.fromMap(e)).toList();
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:contacts_plus_plus/clients/notification_client.dart';
|
||||||
import 'package:contacts_plus_plus/models/authentication_data.dart';
|
import 'package:contacts_plus_plus/models/authentication_data.dart';
|
||||||
import 'package:contacts_plus_plus/models/friend.dart';
|
import 'package:contacts_plus_plus/models/friend.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||||
|
@ -46,10 +47,11 @@ class MessagingClient extends ChangeNotifier {
|
||||||
static const String _negotiationPacket = "{\"protocol\":\"json\", \"version\":1}$eofChar";
|
static const String _negotiationPacket = "{\"protocol\":\"json\", \"version\":1}$eofChar";
|
||||||
static const List<int> _reconnectTimeoutsSeconds = [0, 5, 10, 20, 60];
|
static const List<int> _reconnectTimeoutsSeconds = [0, 5, 10, 20, 60];
|
||||||
static const String taskName = "periodic-unread-check";
|
static const String taskName = "periodic-unread-check";
|
||||||
static const Duration _autoRefreshDuration = Duration(seconds: 90);
|
static const Duration _autoRefreshDuration = Duration(seconds: 10);
|
||||||
static const Duration _refreshTimeoutDuration = Duration(seconds: 30);
|
static const Duration _unreadSafeguardDuration = Duration(seconds: 120);
|
||||||
|
static const String _messageBoxKey = "message-box";
|
||||||
|
static const String _lastUpdateKey = "__last-update-time";
|
||||||
final ApiClient _apiClient;
|
final ApiClient _apiClient;
|
||||||
final Map<String, Friend> _friendsCache = {};
|
|
||||||
final List<Friend> _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build()
|
final List<Friend> _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build()
|
||||||
final Map<String, MessageCache> _messageCache = {};
|
final Map<String, MessageCache> _messageCache = {};
|
||||||
final Map<String, List<Message>> _unreads = {};
|
final Map<String, List<Message>> _unreads = {};
|
||||||
|
@ -60,6 +62,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
Timer? _notifyOnlineTimer;
|
Timer? _notifyOnlineTimer;
|
||||||
Timer? _autoRefresh;
|
Timer? _autoRefresh;
|
||||||
Timer? _refreshTimeout;
|
Timer? _refreshTimeout;
|
||||||
|
Timer? _unreadSafeguard;
|
||||||
int _attempts = 0;
|
int _attempts = 0;
|
||||||
WebSocket? _wsChannel;
|
WebSocket? _wsChannel;
|
||||||
bool _isConnecting = false;
|
bool _isConnecting = false;
|
||||||
|
@ -71,7 +74,11 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient})
|
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient})
|
||||||
: _apiClient = apiClient, _notificationClient = notificationClient {
|
: _apiClient = apiClient, _notificationClient = notificationClient {
|
||||||
refreshFriendsListWithErrorHandler();
|
Hive.openBox(_messageBoxKey).then((box) async {
|
||||||
|
box.delete(_lastUpdateKey);
|
||||||
|
await refreshFriendsListWithErrorHandler();
|
||||||
|
await _refreshUnreads();
|
||||||
|
});
|
||||||
startWebsocket();
|
startWebsocket();
|
||||||
_notifyOnlineTimer = Timer.periodic(const Duration(seconds: 60), (timer) async {
|
_notifyOnlineTimer = Timer.periodic(const Duration(seconds: 60), (timer) async {
|
||||||
// We should probably let the MessagingClient handle the entire state of USerStatus instead of mirroring like this
|
// We should probably let the MessagingClient handle the entire state of USerStatus instead of mirroring like this
|
||||||
|
@ -99,7 +106,16 @@ class MessagingClient extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshFriendsListWithErrorHandler () async {
|
Future<void> _refreshUnreads() async {
|
||||||
|
_unreadSafeguard?.cancel();
|
||||||
|
try {
|
||||||
|
final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
|
||||||
|
updateAllUnreads(unreadMessages.toList());
|
||||||
|
} catch (_) {}
|
||||||
|
_unreadSafeguard = Timer(_unreadSafeguardDuration, _refreshUnreads);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshFriendsListWithErrorHandler () async {
|
||||||
try {
|
try {
|
||||||
await refreshFriendsList();
|
await refreshFriendsList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -109,24 +125,15 @@ class MessagingClient extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refreshFriendsList() async {
|
Future<void> refreshFriendsList() async {
|
||||||
if (_refreshTimeout?.isActive == true) return;
|
DateTime? lastUpdateUtc = Hive.box(_messageBoxKey).get(_lastUpdateKey);
|
||||||
|
|
||||||
_autoRefresh?.cancel();
|
_autoRefresh?.cancel();
|
||||||
_autoRefresh = Timer(_autoRefreshDuration, () => refreshFriendsList());
|
_autoRefresh = Timer(_autoRefreshDuration, () => refreshFriendsList());
|
||||||
_refreshTimeout?.cancel();
|
|
||||||
_refreshTimeout = Timer(_refreshTimeoutDuration, () {});
|
|
||||||
|
|
||||||
final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
|
final friends = await FriendApi.getFriendsList(_apiClient, lastStatusUpdate: lastUpdateUtc);
|
||||||
updateAllUnreads(unreadMessages.toList());
|
|
||||||
|
|
||||||
final friends = await FriendApi.getFriendsList(_apiClient);
|
|
||||||
_friendsCache.clear();
|
|
||||||
for (final friend in friends) {
|
for (final friend in friends) {
|
||||||
_friendsCache[friend.id] = friend;
|
await _updateFriend(friend);
|
||||||
}
|
}
|
||||||
_sortedFriendsCache.clear();
|
|
||||||
_sortedFriendsCache.addAll(friends);
|
|
||||||
_sortFriendsCache();
|
|
||||||
_initStatus = "";
|
_initStatus = "";
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -183,7 +190,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
return _unreads[message.senderId]?.any((element) => element.id == message.id) ?? false;
|
return _unreads[message.senderId]?.any((element) => element.id == message.id) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Friend? getAsFriend(String userId) => _friendsCache[userId];
|
Friend? getAsFriend(String userId) => Friend.fromMapOrNull(Hive.box(_messageBoxKey).get(userId));
|
||||||
|
|
||||||
List<Friend> get cachedFriends => _sortedFriendsCache;
|
List<Friend> get cachedFriends => _sortedFriendsCache;
|
||||||
|
|
||||||
|
@ -196,8 +203,13 @@ class MessagingClient extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateFriend(Friend friend) {
|
Future<void> _updateFriend(Friend friend) async {
|
||||||
_friendsCache[friend.id] = friend;
|
final box = Hive.box(_messageBoxKey);
|
||||||
|
box.put(friend.id, friend.toMap());
|
||||||
|
final lastStatusUpdate = box.get(_lastUpdateKey);
|
||||||
|
if (lastStatusUpdate == null || friend.userStatus.lastStatusChange.isAfter(lastStatusUpdate)) {
|
||||||
|
await box.put(_lastUpdateKey, friend.userStatus.lastStatusChange);
|
||||||
|
}
|
||||||
final sIndex = _sortedFriendsCache.indexWhere((element) => element.id == friend.id);
|
final sIndex = _sortedFriendsCache.indexWhere((element) => element.id == friend.id);
|
||||||
if (sIndex == -1) {
|
if (sIndex == -1) {
|
||||||
_sortedFriendsCache.add(friend);
|
_sortedFriendsCache.add(friend);
|
||||||
|
@ -211,7 +223,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
final friend = getAsFriend(userId);
|
final friend = getAsFriend(userId);
|
||||||
if (friend == null) return;
|
if (friend == null) return;
|
||||||
final newStatus = await UserApi.getUserStatus(_apiClient, userId: userId);
|
final newStatus = await UserApi.getUserStatus(_apiClient, userId: userId);
|
||||||
_updateFriend(friend.copyWith(userStatus: newStatus));
|
await _updateFriend(friend.copyWith(userStatus: newStatus));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import 'package:contacts_plus_plus/widgets/update_notifier.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -26,8 +28,9 @@ void main() async {
|
||||||
isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
|
isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await Hive.initFlutter();
|
||||||
Logger.root.onRecord.listen((event) => log(event.message, name: event.loggerName, time: event.time));
|
final dateFormat = DateFormat.Hms();
|
||||||
|
Logger.root.onRecord.listen((event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time));
|
||||||
final settingsClient = SettingsClient();
|
final settingsClient = SettingsClient();
|
||||||
await settingsClient.loadSettings();
|
await settingsClient.loadSettings();
|
||||||
runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,)));
|
runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,)));
|
||||||
|
|
|
@ -33,6 +33,11 @@ class Friend implements Comparable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Friend? fromMapOrNull(Map? map) {
|
||||||
|
if (map == null) return null;
|
||||||
|
return Friend.fromMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
Friend copyWith({
|
Friend copyWith({
|
||||||
String? id, String? username, String? ownerId, UserStatus? userStatus, UserProfile? userProfile,
|
String? id, String? username, String? ownerId, UserStatus? userStatus, UserProfile? userProfile,
|
||||||
FriendStatus? friendStatus, DateTime? latestMessageTime}) {
|
FriendStatus? friendStatus, DateTime? latestMessageTime}) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/apis/user_api.dart';
|
import 'package:contacts_plus_plus/apis/user_api.dart';
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
|
@ -134,13 +133,11 @@ class _FriendsListState extends State<FriendsList> {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2)
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2)
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
|
||||||
_userStatusFuture = null;
|
|
||||||
});
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_userStatusFuture = UserApi.getUserStatus(clientHolder.apiClient, userId: clientHolder.apiClient
|
_userStatusFuture = UserApi.getUserStatus(clientHolder.apiClient, userId: clientHolder.apiClient
|
||||||
.userId);
|
.userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.warning),
|
icon: const Icon(Icons.warning),
|
||||||
label: const Text("Retry"),
|
label: const Text("Retry"),
|
||||||
|
|
16
pubspec.lock
16
pubspec.lock
|
@ -272,6 +272,22 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
hive:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive
|
||||||
|
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
|
hive_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive_flutter
|
||||||
|
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
html:
|
html:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -56,6 +56,8 @@ dependencies:
|
||||||
photo_view: ^0.14.0
|
photo_view: ^0.14.0
|
||||||
color: ^3.0.0
|
color: ^3.0.0
|
||||||
dynamic_color: ^1.6.5
|
dynamic_color: ^1.6.5
|
||||||
|
hive: ^2.2.3
|
||||||
|
hive_flutter: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue