Skip to content

Providers_tracker #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/archethic_dapp_framework_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export 'src/application/def_tokens.dart';
export 'src/application/oracle/provider.dart';
export 'src/application/oracle/state.dart';
export 'src/application/ucids_tokens.dart';
export 'src/application/utils/providers_logger.dart';
export 'src/application/utils/providers_tracker.dart';
export 'src/application/verified_tokens.dart';
export 'src/application/version.dart';
export 'src/domain/models/ae_token.dart';
Expand Down Expand Up @@ -76,7 +78,6 @@ export 'src/util/address_util.dart';
export 'src/util/custom_logs.dart';
export 'src/util/file_util.dart';
export 'src/util/generic/get_it_instance.dart';
export 'src/util/generic/providers_observer.dart';
export 'src/util/logger_output.dart';
export 'src/util/periodic_future.dart';
export 'src/util/router_util.dart';
Expand Down
12 changes: 9 additions & 3 deletions lib/src/application/oracle/provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ import 'dart:async';

import 'package:archethic_dapp_framework_flutter/src/application/oracle/state.dart';
import 'package:archethic_lib_dart/archethic_lib_dart.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'provider.g.dart';

@riverpod
OracleService oracleService(Ref ref) {
// We use always mainnet values
return OracleService('https://mainnet.archethic.net');
}

@riverpod
class _ArchethicOracleUCONotifier extends _$ArchethicOracleUCONotifier {
StreamSubscription? archethicOracleSubscription;
final OracleService _oracleService =
OracleService('https://mainnet.archethic.net');

late final OracleService _oracleService;
static final _logger = Logger('ArchethicOracleUCONotifier');

@override
Future<ArchethicOracleUCO> build() async {
_oracleService = ref.watch(oracleServiceProvider);
ref.onDispose(stopSubscription);

return _subscribe();
Expand Down
19 changes: 18 additions & 1 deletion lib/src/application/oracle/provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

212 changes: 212 additions & 0 deletions lib/src/application/utils/providers_tracker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import 'dart:async';
import 'dart:core';

import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

/// Keeps track of alive providers.
///
/// > Usage in production is not recommended.
///
/// # Howto use
/// ## Register the observer
///
/// ```dart
/// import 'package:archethic_dapp_framework_flutter/archethic_dapp_framework_flutter.dart' as aedappfm;
///
/// runApp(
/// ProviderScope(
/// observers: [
/// if (kDebugMode) aedappfm.ProvidersTracker(),
/// ],
/// child: const MyApp(),
/// ),
/// ;
/// ```
///
/// ## Check all alive providers
///
/// In debug console, check `ProvidersTracker` content :
///
/// ### Command :
/// ```dart
/// aedappfm.ProvidersTracker().aliveProviders
/// ```
///
/// ### Result :
/// ```dart
/// Set
/// [0] = AutoDisposeProvider (oracleServiceProvider:AutoDisposeProvider<OracleService>#2d1c8)
/// [1] = AutoDisposeAsyncNotifierProviderImpl (_archethicOracleUCONotifierProvider:AutoDisposeAsyncNotifierProviderImpl<_ArchethicOracleUCONotifier, ArchethicOracleUCO>#4aa30)
/// [2] = AutoDisposeProvider (apiServiceProvider:AutoDisposeProvider<ApiService>#e4552)
/// ```
///
/// ## Filter and READ providers
///
/// In debug console :
///
/// ### Command :
/// ```dart
/// aedappfm.ProvidersTracker().byName('oracle').read
/// ```
///
/// ### Result :
/// ```dart
/// Set
/// [0] = AutoDisposeProvider (oracleServiceProvider:AutoDisposeProvider<OracleService>#2d1c8)
/// [1] = AutoDisposeAsyncNotifierProviderImpl (_archethicOracleUCONotifierProvider:AutoDisposeAsyncNotifierProviderImpl<_ArchethicOracleUCONotifier, ArchethicOracleUCO>#4aa30)
/// ```

/// ## Filter and WATCH providers
///
/// In debug console :
///
/// ### Command :
/// ```dart
/// // watch returns a stream. Here we just log the number of providers whose name matches 'oracle'
/// aedappfm.ProvidersTracker().byName('oracle').watch.forEach((providers) => print('>>> Oracle : ${providers.length}'))
/// ```
///
/// ### Result :
///
/// Each time the alive providers matching 'oracle' changes, we have a log like this :
///
/// ```dart
/// >>> Oracle : 2
/// ```
class ProvidersTracker extends ProviderObserver {
factory ProvidersTracker() {
return _instance ??= ProvidersTracker._();
}
ProvidersTracker._();

static ProvidersTracker? _instance;

final ValueNotifier<Set<ProviderBase<Object?>>> _aliveProviders =
ValueNotifier({});

void _addAliveProvider(ProviderBase<Object?> provider) {
_aliveProviders.value = {..._aliveProviders.value, provider};
}

void _removeAliveProvider(ProviderBase<Object?> provider) {
_aliveProviders.value = _aliveProviders.value
.where((aliveProvider) => aliveProvider != provider)
.toSet();
}

@override
void didAddProvider(
ProviderBase<Object?> provider,
Object? value,
ProviderContainer container,
) {
_addAliveProvider(provider);
}

@override
void didDisposeProvider(ProviderBase provider, ProviderContainer container) {
_removeAliveProvider(provider);
}

/// Shows all providers currently alive
Set<ProviderBase<Object?>> get aliveProviders => _aliveProviders.value;

/// Shows the provider with matching [hashCode]
ProviderBase<Object?>? provider(int hashCode) => _aliveProviders.value
.where(
(element) => element.hashCode == hashCode,
)
.firstOrNull;

/// Creates a [ProvidersTrackerMatcher].
ProvidersTrackerMatcher match(ProviderMatcher matcher) =>
ProvidersTrackerMatcher(tracker: this, matcher: matcher);

/// Creates a [ProvidersTrackerMatcher] which
/// filters providers by name/classname.
///
/// For more details about the matchin rules, check [NameProviderMatcher].
ProvidersTrackerMatcher byName(String name) =>
match(ProviderMatcher.name(name));
}

/// Provides [read] and [watch] methods to monitor
/// currently alive providers filtered with [matcher].
class ProvidersTrackerMatcher {
ProvidersTrackerMatcher({
required this.tracker,
required this.matcher,
});

final ProviderMatcher matcher;
final ProvidersTracker tracker;

/// Shows all providers currently alive, filtered according to the [matcher].
Set<ProviderBase<Object?>> get read =>
tracker._aliveProviders.value.match(matcher);

/// Creates a [Stream] watching all providers currently alive, filtered according to the [matcher].
Stream<Set<ProviderBase<Object?>>> get watch {
Set<ProviderBase<Object?>>? previousValue;
late final StreamController<Set<ProviderBase<Object?>>> controller;

void processChange() {
final newValue = tracker._aliveProviders.value.match(matcher);
if (newValue == previousValue) return;

previousValue = newValue;
controller.add(newValue);
}

void listen() {
processChange();
tracker._aliveProviders.addListener(processChange);
}

void close() {
tracker._aliveProviders.removeListener(processChange);
}

controller = StreamController<Set<ProviderBase<Object?>>>(
onListen: listen,
onPause: close,
onResume: listen,
onCancel: close,
);

return controller.stream;
}
}

abstract class ProviderMatcher {
/// [name] matcher is case insensitive. Name matching is quite permissive.
///
/// `ProvidersTracker().aliveProviders('oracle')` would match the following providers :
///
/// - AutoDisposeProvider (oracleServiceProvider:AutoDisposeProvider<OracleService>#2d1c8)
/// - AutoDisposeAsyncNotifierProviderImpl (_archethicOracleUCONotifierProvider:AutoDisposeAsyncNotifierProviderImpl<_ArchethicOracleUCONotifier, ArchethicOracleUCO>#4aa30)
factory ProviderMatcher.name(String name) => NameProviderMatcher(name);

bool matches(ProviderBase<Object?> provider) => throw UnimplementedError();
}

class NameProviderMatcher implements ProviderMatcher {
const NameProviderMatcher(this.name);

final String name;

@override
bool matches(ProviderBase<Object?> provider) =>
(provider.name ?? '').toLowerCase().contains(name.toLowerCase()) ||
provider.runtimeType
.toString()
.toLowerCase()
.contains(name.toLowerCase());
}

extension SetProviderMatchExt on Set<ProviderBase<Object?>> {
Set<ProviderBase<Object?>> match(ProviderMatcher matcher) => where(
(element) => matcher.matches(element),
).toSet();
}
Loading