diff --git a/flutter_client/lib/cache/connectivity_provider.dart b/flutter_client/lib/cache/connectivity_provider.dart index 4e175661..06fa3e6c 100644 --- a/flutter_client/lib/cache/connectivity_provider.dart +++ b/flutter_client/lib/cache/connectivity_provider.dart @@ -17,6 +17,19 @@ final connectivityProvider = StreamProvider((ref) async* { bool isOnline(List rs) => rs.any((r) => r != ConnectivityResult.none); - yield isOnline(await c.checkConnectivity()); + // checkConnectivity() goes over a platform channel and on some + // Android builds it can stall. Fall back to "assume online" after + // 2s so consumers waiting on .future never block forever — being + // wrong about connectivity costs at most one failed request, but + // being stuck spins the UI indefinitely. + try { + final initial = await c.checkConnectivity().timeout( + const Duration(seconds: 2), + onTimeout: () => const [ConnectivityResult.wifi], + ); + yield isOnline(initial); + } catch (_) { + yield true; + } yield* c.onConnectivityChanged.map(isOnline); }); diff --git a/flutter_client/lib/library/library_providers.dart b/flutter_client/lib/library/library_providers.dart index 2a5b7908..256d5056 100644 --- a/flutter_client/lib/library/library_providers.dart +++ b/flutter_client/lib/library/library_providers.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:drift/drift.dart' as drift; +import 'package:flutter/foundation.dart' show debugPrint; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/client.dart'; @@ -154,24 +155,42 @@ final albumProvider = StreamProvider.family< await for (final albumRows in albumQuery.watch()) { if (albumRows.isEmpty) { + debugPrint('albumProvider($albumId): drift miss, attempting cold-cache fetch'); // Cold cache fallback - if (await ref.read(connectivityProvider.future)) { + bool online; + try { + online = await ref + .read(connectivityProvider.future) + .timeout(const Duration(seconds: 3), onTimeout: () => true); + } catch (e) { + debugPrint('albumProvider($albumId): connectivity check threw $e — assuming online'); + online = true; + } + debugPrint('albumProvider($albumId): online=$online'); + if (online) { try { final api = await ref.read(libraryApiProvider.future); - final fresh = await api.getAlbum(albumId); + debugPrint('albumProvider($albumId): calling getAlbum'); + final fresh = await api + .getAlbum(albumId) + .timeout(const Duration(seconds: 10)); + debugPrint('albumProvider($albumId): got ${fresh.tracks.length} tracks, writing to drift'); await db.batch((b) { b.insertAllOnConflictUpdate(db.cachedAlbums, [fresh.album.toDrift()]); b.insertAllOnConflictUpdate(db.cachedTracks, fresh.tracks.map((t) => t.toDrift()).toList()); }); + debugPrint('albumProvider($albumId): drift write done; awaiting watch re-emit'); // watch() re-emits with the populated rows; loop continues. - } catch (_) { + } catch (e, st) { + debugPrint('albumProvider($albumId): cold-cache fetch failed: $e\n$st'); yield ( album: const AlbumRef(id: '', title: '', artistId: ''), tracks: const [], ); } } else { + debugPrint('albumProvider($albumId): offline, yielding empty'); yield ( album: const AlbumRef(id: '', title: '', artistId: ''), tracks: const [], @@ -179,6 +198,7 @@ final albumProvider = StreamProvider.family< } continue; } + debugPrint('albumProvider($albumId): drift hit (${albumRows.length} rows)'); final albumRow = albumRows.first; final album = albumRow.readTable(db.cachedAlbums).toRef(