内存泄露闪退以及切房公屏显示问题
This commit is contained in:
parent
fec672dfb6
commit
2a1b63de24
@ -26,6 +26,7 @@ import 'app/routes/sc_routes.dart';
|
||||
import 'app/routes/sc_lk_application.dart';
|
||||
import 'shared/tools/sc_deep_link_handler.dart';
|
||||
import 'shared/tools/sc_deviceId_utils.dart';
|
||||
import 'shared/tools/sc_gift_vap_svga_manager.dart';
|
||||
import 'shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'modules/splash/splash_page.dart';
|
||||
import 'services/general/sc_app_general_manager.dart';
|
||||
@ -105,6 +106,15 @@ void _configureImageCache() {
|
||||
imageCache.maximumSize = SCGlobalConfig.recommendedImageCacheEntries;
|
||||
}
|
||||
|
||||
void _releaseVisualMemoryCaches({bool clearLiveImages = false}) {
|
||||
SCGiftVapSvgaManager().clearMemoryCache();
|
||||
final imageCache = PaintingBinding.instance.imageCache;
|
||||
imageCache.clear();
|
||||
if (clearLiveImages) {
|
||||
imageCache.clearLiveImages();
|
||||
}
|
||||
}
|
||||
|
||||
void _installErrorHandlers() {
|
||||
FlutterError.onError = (details) {
|
||||
FlutterError.presentError(details);
|
||||
@ -303,6 +313,14 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.inactive ||
|
||||
state == AppLifecycleState.hidden) {
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
_releaseVisualMemoryCaches(
|
||||
clearLiveImages: state == AppLifecycleState.paused,
|
||||
);
|
||||
}
|
||||
if (state == AppLifecycleState.detached) {
|
||||
unawaited(
|
||||
context.read<RtcProvider>().releaseRtcEngineForAppTermination(),
|
||||
@ -310,6 +328,13 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didHaveMemoryPressure() {
|
||||
super.didHaveMemoryPressure();
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
_releaseVisualMemoryCaches(clearLiveImages: true);
|
||||
}
|
||||
|
||||
Future<void> _initLink() async {
|
||||
// 初始化,并传递一个回调函数用于处理链接
|
||||
await _deepLinkHandler.initDeepLinks(
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@ -364,7 +365,7 @@ class SCLoginWithAccountPageState extends State<SCLoginWithAccountPage>
|
||||
}
|
||||
|
||||
///登录
|
||||
_login() async {
|
||||
Future<void> _login() async {
|
||||
String account = accountController.text;
|
||||
String pass = passController.text;
|
||||
if (account.isEmpty || pass.isEmpty) {
|
||||
@ -394,15 +395,47 @@ class SCLoginWithAccountPageState extends State<SCLoginWithAccountPage>
|
||||
}
|
||||
AccountStorage().setCurrentUser(user);
|
||||
SCLoadingManager.hide();
|
||||
DataPersistence.setString("Login_Account", account);
|
||||
DataPersistence.setString("Login_Pwd", pass);
|
||||
try {
|
||||
await Future.wait([
|
||||
DataPersistence.setString("Login_Account", account),
|
||||
DataPersistence.setString("Login_Pwd", pass),
|
||||
]);
|
||||
} catch (_) {}
|
||||
if (mounted) {
|
||||
SCNavigatorUtils.push(context, SCRoutes.home, clearStack: true);
|
||||
}
|
||||
} catch (e) {
|
||||
SCLoadingManager.hide();
|
||||
// 可以添加错误处理逻辑
|
||||
rethrow;
|
||||
final message = _loginErrorMessage(e);
|
||||
if (message.isNotEmpty) {
|
||||
SCTts.show(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _loginErrorMessage(Object error) {
|
||||
if (error is DioException) {
|
||||
final responseData = error.response?.data;
|
||||
if (responseData is Map) {
|
||||
final serverMessage = responseData["errorMsg"]?.toString().trim();
|
||||
if (serverMessage != null && serverMessage.isNotEmpty) {
|
||||
return serverMessage;
|
||||
}
|
||||
}
|
||||
final dioError = error.error?.toString().trim();
|
||||
if (dioError != null && dioError.isNotEmpty) {
|
||||
return dioError.replaceFirst("Exception: ", "");
|
||||
}
|
||||
final message = error.message?.trim();
|
||||
if (message != null && message.isNotEmpty) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
final message = error.toString().replaceFirst("Exception: ", "").trim();
|
||||
if (message.isNotEmpty) {
|
||||
return message;
|
||||
}
|
||||
return "Login failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ class _AllChatPageState extends State<AllChatPage> {
|
||||
bool _isDisposed = false;
|
||||
final List<Msg> _msgList = [];
|
||||
late RtmProvider provider;
|
||||
late final RoomNewMsgListener _newMsgListener = _onNewMsg;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -28,7 +29,7 @@ class _AllChatPageState extends State<AllChatPage> {
|
||||
_controller.addListener(_scrollListener);
|
||||
provider = Provider.of<RtmProvider>(context, listen: false);
|
||||
final msgList = provider.roomAllMsgList;
|
||||
provider.msgAllListener = _onNewMsg;
|
||||
provider.msgAllListener = _newMsgListener;
|
||||
_msgList.addAll(msgList);
|
||||
// 使用安全的方式初始化滚动位置
|
||||
_initScrollPosition();
|
||||
@ -81,7 +82,9 @@ class _AllChatPageState extends State<AllChatPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
provider.msgAllListener = null;
|
||||
if (identical(provider.msgAllListener, _newMsgListener)) {
|
||||
provider.msgAllListener = null;
|
||||
}
|
||||
_controller.removeListener(_scrollListener);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
|
||||
@ -21,6 +21,7 @@ class _ChatPageState extends State<ChatPage> {
|
||||
bool _isDisposed = false;
|
||||
List<Msg> _msgList = [];
|
||||
late RtmProvider provider;
|
||||
late final RoomNewMsgListener _newMsgListener = _onNewMsg;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -28,7 +29,7 @@ class _ChatPageState extends State<ChatPage> {
|
||||
_controller.addListener(_scrollListener);
|
||||
provider = Provider.of<RtmProvider>(context, listen: false);
|
||||
var msgList = provider.roomChatMsgList;
|
||||
provider.msgChatListener = _onNewMsg;
|
||||
provider.msgChatListener = _newMsgListener;
|
||||
_msgList.addAll(msgList ??= []);
|
||||
// 使用安全的方式初始化滚动位置
|
||||
_initScrollPosition();
|
||||
@ -80,7 +81,9 @@ class _ChatPageState extends State<ChatPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
provider.msgChatListener = null;
|
||||
if (identical(provider.msgChatListener, _newMsgListener)) {
|
||||
provider.msgChatListener = null;
|
||||
}
|
||||
_controller.removeListener(_scrollListener);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
|
||||
@ -21,6 +21,7 @@ class _GiftChatPageState extends State<GiftChatPage> {
|
||||
bool _isDisposed = false;
|
||||
final List<Msg> _msgList = [];
|
||||
late RtmProvider provider;
|
||||
late final RoomNewMsgListener _newMsgListener = _onNewMsg;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -28,7 +29,7 @@ class _GiftChatPageState extends State<GiftChatPage> {
|
||||
_controller.addListener(_scrollListener);
|
||||
provider = Provider.of<RtmProvider>(context, listen: false);
|
||||
final msgList = provider.roomGiftMsgList;
|
||||
provider.msgGiftListener = _onNewMsg;
|
||||
provider.msgGiftListener = _newMsgListener;
|
||||
_msgList.addAll(msgList);
|
||||
// 使用安全的方式初始化滚动位置
|
||||
_initScrollPosition();
|
||||
@ -81,7 +82,9 @@ class _GiftChatPageState extends State<GiftChatPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
provider.msgGiftListener = null;
|
||||
if (identical(provider.msgGiftListener, _newMsgListener)) {
|
||||
provider.msgGiftListener = null;
|
||||
}
|
||||
_controller.removeListener(_scrollListener);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
|
||||
@ -18,6 +18,8 @@ class DataPersistence {
|
||||
static bool _isInitialized = false;
|
||||
static Completer<void>? _initializationCompleter;
|
||||
|
||||
static bool get isInitialized => _isInitialized && _prefs != null;
|
||||
|
||||
// 初始化 SharedPreferences
|
||||
static Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
@ -48,9 +50,17 @@ class DataPersistence {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _ensureInitialized() async {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
await initialize();
|
||||
_checkInitialized();
|
||||
}
|
||||
|
||||
// 存储字符串
|
||||
static Future<bool> setString(String key, String value) async {
|
||||
_checkInitialized();
|
||||
await _ensureInitialized();
|
||||
return await _prefs!.setString(key, value);
|
||||
}
|
||||
|
||||
@ -62,7 +72,7 @@ class DataPersistence {
|
||||
|
||||
// 存储整数
|
||||
static Future<bool> setInt(String key, int value) async {
|
||||
_checkInitialized();
|
||||
await _ensureInitialized();
|
||||
return await _prefs!.setInt(key, value);
|
||||
}
|
||||
|
||||
@ -74,7 +84,7 @@ class DataPersistence {
|
||||
|
||||
// 存储布尔值
|
||||
static Future<bool> setBool(String key, bool value) async {
|
||||
_checkInitialized();
|
||||
await _ensureInitialized();
|
||||
return await _prefs!.setBool(key, value);
|
||||
}
|
||||
|
||||
@ -86,7 +96,7 @@ class DataPersistence {
|
||||
|
||||
// 存储双精度浮点数
|
||||
static Future<bool> setDouble(String key, double value) async {
|
||||
_checkInitialized();
|
||||
await _ensureInitialized();
|
||||
return await _prefs!.setDouble(key, value);
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,10 @@ class AccountStorage {
|
||||
}
|
||||
var userJson = DataPersistence.getCurrentUser();
|
||||
if (userJson.isNotEmpty) {
|
||||
_currentUser = SocialChatLoginRes.fromJson(jsonDecode(userJson));
|
||||
_currentUser = _decodeCurrentUser(userJson);
|
||||
if (_currentUser == null) {
|
||||
unawaited(_clearPersistedSession(clearDurable: false));
|
||||
}
|
||||
}
|
||||
return _currentUser;
|
||||
}
|
||||
@ -46,16 +49,10 @@ class AccountStorage {
|
||||
///同步优化数据
|
||||
void setCurrentUser(SocialChatLoginRes user) {
|
||||
_currentUser = user;
|
||||
setToken(_currentUser!.token ?? "");
|
||||
token = _currentUser!.token ?? "";
|
||||
String userJson = jsonEncode(_currentUser?.toJson());
|
||||
if (userJson.isNotEmpty) {
|
||||
DataPersistence.setCurrentUser(userJson);
|
||||
unawaited(
|
||||
DurableAuthStorage.saveSession(
|
||||
token: _currentUser!.token ?? "",
|
||||
currentUserJson: userJson,
|
||||
),
|
||||
);
|
||||
unawaited(_persistSession(token: token, currentUserJson: userJson));
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,54 +127,110 @@ class AccountStorage {
|
||||
Future<void> restoreDurableSessionIfNeeded() async {
|
||||
final localToken = DataPersistence.getToken().trim();
|
||||
final localUserJson = DataPersistence.getCurrentUser().trim();
|
||||
if (localToken.isNotEmpty && localUserJson.isNotEmpty) {
|
||||
final localUser = _decodeCurrentUser(localUserJson);
|
||||
if (localToken.isNotEmpty && localUser != null) {
|
||||
token = localToken;
|
||||
_currentUser = localUser;
|
||||
return;
|
||||
}
|
||||
|
||||
final session = await DurableAuthStorage.readSession();
|
||||
if (session == null) {
|
||||
if (localToken.isNotEmpty || localUserJson.isNotEmpty) {
|
||||
unawaited(_clearPersistedSession(clearDurable: false));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
token = session.token;
|
||||
await DataPersistence.setToken(session.token);
|
||||
await DataPersistence.setCurrentUser(session.currentUserJson);
|
||||
try {
|
||||
_currentUser = SocialChatLoginRes.fromJson(
|
||||
jsonDecode(session.currentUserJson),
|
||||
);
|
||||
} catch (_) {
|
||||
_currentUser = null;
|
||||
_currentUser = _decodeCurrentUser(session.currentUserJson);
|
||||
if (_currentUser == null) {
|
||||
token = "";
|
||||
await _clearPersistedSession();
|
||||
return;
|
||||
}
|
||||
await _persistSession(
|
||||
token: session.token,
|
||||
currentUserJson: session.currentUserJson,
|
||||
);
|
||||
}
|
||||
|
||||
void setToken(String tk) {
|
||||
token = tk;
|
||||
DataPersistence.setToken(token);
|
||||
unawaited(_persistToken(token));
|
||||
}
|
||||
|
||||
void _cleanUser() {
|
||||
token = "";
|
||||
_currentUser = null;
|
||||
DataPersistence.setToken("");
|
||||
DataPersistence.setCurrentUser("");
|
||||
unawaited(_clearPersistedSession());
|
||||
unawaited(DataPersistence.clearPendingChannelAuth());
|
||||
unawaited(DurableAuthStorage.clearSession());
|
||||
}
|
||||
|
||||
///退出登录
|
||||
void logout(BuildContext context) {
|
||||
_cleanUser();
|
||||
SCHeartbeatUtils.cancelTimer();
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).exitCurrentVoiceRoomSession(true);
|
||||
Provider.of<RtmProvider>(context, listen: false).logout();
|
||||
try {
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).exitCurrentVoiceRoomSession(true);
|
||||
} catch (_) {}
|
||||
try {
|
||||
Provider.of<RtmProvider>(context, listen: false).logout();
|
||||
} catch (_) {}
|
||||
SCNavigatorUtils.pushLoginIfNeeded(context, clearStack: true);
|
||||
SCRoomUtils.closeAllDialogs();
|
||||
try {
|
||||
SCRoomUtils.closeAllDialogs();
|
||||
} catch (_) {}
|
||||
SCMessageUtils.redPacketFutureCache.clear();
|
||||
SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults();
|
||||
OverlayManager().dispose();
|
||||
try {
|
||||
OverlayManager().dispose();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
SocialChatLoginRes? _decodeCurrentUser(String userJson) {
|
||||
if (userJson.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final decoded = jsonDecode(userJson);
|
||||
if (decoded is Map) {
|
||||
return SocialChatLoginRes.fromJson(Map<String, dynamic>.from(decoded));
|
||||
}
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _persistToken(String value) async {
|
||||
try {
|
||||
await DataPersistence.setToken(value);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _persistSession({
|
||||
required String token,
|
||||
required String currentUserJson,
|
||||
}) async {
|
||||
try {
|
||||
await DataPersistence.setToken(token);
|
||||
await DataPersistence.setCurrentUser(currentUserJson);
|
||||
} catch (_) {}
|
||||
await DurableAuthStorage.saveSession(
|
||||
token: token,
|
||||
currentUserJson: currentUserJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _clearPersistedSession({bool clearDurable = true}) async {
|
||||
try {
|
||||
await DataPersistence.setToken("");
|
||||
await DataPersistence.setCurrentUser("");
|
||||
} catch (_) {}
|
||||
if (clearDurable) {
|
||||
await DurableAuthStorage.clearSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,8 +170,9 @@ class SCGiftVapSvgaManager {
|
||||
'start preload path=$path active=$_activePreloadCount '
|
||||
'queueRemaining=${_preloadQueue.length}',
|
||||
);
|
||||
_warmupPath(path).whenComplete(() {
|
||||
_activePreloadCount--;
|
||||
_warmupPath(path).catchError((_) {}).whenComplete(() {
|
||||
_activePreloadCount =
|
||||
_activePreloadCount > 0 ? _activePreloadCount - 1 : 0;
|
||||
_log(
|
||||
'finish preload path=$path active=$_activePreloadCount '
|
||||
'queueRemaining=${_preloadQueue.length}',
|
||||
@ -263,9 +264,12 @@ class SCGiftVapSvgaManager {
|
||||
}
|
||||
|
||||
void _cacheSvgaEntity(String path, MovieEntity entity) {
|
||||
videoItemCache.remove(path);
|
||||
final previous = videoItemCache.remove(path);
|
||||
if (previous != null && !identical(previous, entity)) {
|
||||
_disposeSvgaEntityIfUnused(previous);
|
||||
}
|
||||
videoItemCache[path] = entity;
|
||||
_trimResolvedCache(videoItemCache, _maxSvgaCacheEntries);
|
||||
_trimSvgaCache();
|
||||
}
|
||||
|
||||
String? _touchCachedPlayablePath(String path) {
|
||||
@ -282,6 +286,22 @@ class SCGiftVapSvgaManager {
|
||||
_trimResolvedCache(_playablePathCache, _maxPlayablePathCacheEntries);
|
||||
}
|
||||
|
||||
void _trimSvgaCache() {
|
||||
while (videoItemCache.length > _maxSvgaCacheEntries) {
|
||||
final removableEntry = videoItemCache.entries.firstWhere(
|
||||
(entry) => !_isSvgaEntityInUse(entry.value),
|
||||
orElse: () => videoItemCache.entries.first,
|
||||
);
|
||||
if (_isSvgaEntityInUse(removableEntry.value)) {
|
||||
videoItemCache.remove(removableEntry.key);
|
||||
videoItemCache[removableEntry.key] = removableEntry.value;
|
||||
break;
|
||||
}
|
||||
videoItemCache.remove(removableEntry.key);
|
||||
_disposeSvgaEntity(removableEntry.value);
|
||||
}
|
||||
}
|
||||
|
||||
void _trimResolvedCache<T>(Map<String, T> cache, int maxEntries) {
|
||||
while (cache.length > maxEntries) {
|
||||
final oldestKey = cache.keys.first;
|
||||
@ -554,7 +574,7 @@ class SCGiftVapSvgaManager {
|
||||
} else {
|
||||
_finishCurrentTask();
|
||||
}
|
||||
} catch (e, s) {
|
||||
} catch (_) {
|
||||
_finishCurrentTask();
|
||||
}
|
||||
}
|
||||
@ -728,6 +748,39 @@ class SCGiftVapSvgaManager {
|
||||
SCRoomEffectScheduler().clearHighCostTasks(reason: 'stop_playback');
|
||||
}
|
||||
|
||||
void clearMemoryCache({bool includeCurrent = false}) {
|
||||
_preloadQueue.clear();
|
||||
_queuedPreloadPaths.clear();
|
||||
_activePreloadCount = 0;
|
||||
final entries = videoItemCache.entries.toList(growable: false);
|
||||
for (final entry in entries) {
|
||||
final entity = entry.value;
|
||||
if (!includeCurrent && _isSvgaEntityInUse(entity)) {
|
||||
continue;
|
||||
}
|
||||
videoItemCache.remove(entry.key);
|
||||
_disposeSvgaEntity(entity);
|
||||
}
|
||||
_playablePathCache.clear();
|
||||
}
|
||||
|
||||
bool _isSvgaEntityInUse(MovieEntity entity) {
|
||||
return identical(_rsc?.videoItem, entity);
|
||||
}
|
||||
|
||||
void _disposeSvgaEntityIfUnused(MovieEntity entity) {
|
||||
if (_isSvgaEntityInUse(entity)) {
|
||||
return;
|
||||
}
|
||||
_disposeSvgaEntity(entity);
|
||||
}
|
||||
|
||||
void _disposeSvgaEntity(MovieEntity entity) {
|
||||
try {
|
||||
entity.dispose();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// 释放资源
|
||||
void dispose() {
|
||||
_log('dispose queue=$_queueSummary currentPath=${_currentTask?.path}');
|
||||
@ -735,7 +788,7 @@ class SCGiftVapSvgaManager {
|
||||
stopPlayback();
|
||||
_svgaLoadTasks.clear();
|
||||
_playablePathTasks.clear();
|
||||
videoItemCache.clear();
|
||||
clearMemoryCache(includeCurrent: true);
|
||||
_playablePathCache.clear();
|
||||
_rgc?.dispose();
|
||||
_rgc = null;
|
||||
|
||||
@ -55,14 +55,17 @@ class SCHeartbeatUtils {
|
||||
}
|
||||
|
||||
///上麦主播发送心跳
|
||||
static void scheduleAnchorHeartbeat(String roomId) async {
|
||||
static void scheduleAnchorHeartbeat(String roomId) {
|
||||
cancelAnchorTimer();
|
||||
scheduleHeartbeat(_c, true, roomId: roomId);
|
||||
SCAccountRepository().anchorHeartbeat(roomId).whenComplete(() {
|
||||
_a ??= Timer.periodic(Duration(seconds: 60), (timer) {
|
||||
SCAccountRepository().anchorHeartbeat(roomId).catchError((_) => {});
|
||||
});
|
||||
});
|
||||
unawaited(scheduleHeartbeat(_c, true, roomId: roomId));
|
||||
SCAccountRepository()
|
||||
.anchorHeartbeat(roomId)
|
||||
.then((_) {
|
||||
_a ??= Timer.periodic(Duration(seconds: 60), (timer) {
|
||||
SCAccountRepository().anchorHeartbeat(roomId).catchError((_) => {});
|
||||
});
|
||||
})
|
||||
.catchError((_) {});
|
||||
}
|
||||
|
||||
static void cancelAnchorTimer() {
|
||||
|
||||
26
test/account_storage_session_test.dart
Normal file
26
test/account_storage_session_test.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
tearDown(() {
|
||||
DataPersistence.reset();
|
||||
});
|
||||
|
||||
test('corrupted cached user session is ignored and cleared', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'token': 'stale-token',
|
||||
'currentUser': '{bad json',
|
||||
});
|
||||
await DataPersistence.initialize();
|
||||
|
||||
expect(AccountStorage().getCurrentUser(), isNull);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(DataPersistence.getToken(), isEmpty);
|
||||
expect(DataPersistence.getCurrentUser(), isEmpty);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user