Skip to content

Commit

Permalink
feat: Adding ScopedQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
mathrunet committed Dec 2, 2022
1 parent f2937a3 commit cd6959a
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 6 deletions.
7 changes: 6 additions & 1 deletion packages/katana_scoped/example/lib/main.dart
Expand Up @@ -25,12 +25,17 @@ class MyApp extends StatelessWidget {
}
}

final valueNotifierQuery =
ChangeNotifierScopedQueryFamily<ValueNotifier<int>, int>(
(p) => ValueNotifier(p),
);

class CounterPage extends PageScopedWidget {
const CounterPage({super.key});

@override
Widget build(BuildContext context, PageRef ref) {
final counter = ref.page.watch(() => ValueNotifier(0));
final counter = ref.page.query(valueNotifierQuery(100));

return Scaffold(
appBar: AppBar(title: const Text("App Demo")),
Expand Down
4 changes: 2 additions & 2 deletions packages/katana_scoped/example/pubspec.lock
Expand Up @@ -213,7 +213,7 @@ packages:
name: freezed
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
version: "2.3.0"
freezed_annotation:
dependency: transitive
description:
Expand Down Expand Up @@ -353,7 +353,7 @@ packages:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.0.3"
package_config:
dependency: transitive
description:
Expand Down
1 change: 0 additions & 1 deletion packages/katana_scoped/example/test/dispose.dart
Expand Up @@ -7,7 +7,6 @@

// ignore_for_file: avoid_print

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
Expand Down
1 change: 1 addition & 0 deletions packages/katana_scoped/lib/katana_scoped.dart
Expand Up @@ -23,3 +23,4 @@ part 'src/scoped_value.dart';
part 'src/scoped_value_container.dart';
part 'src/scoped_value_listener.dart';
part 'src/scoped_value_ref.dart';
part 'src/scoped_query.dart';
5 changes: 4 additions & 1 deletion packages/katana_scoped/lib/src/extensions.dart
Expand Up @@ -11,7 +11,10 @@ extension ScopedWidgetBuildContextExtensions on BuildContext {
/// [BuildContext]を渡して祖先にある[TWidget]をO(1)で取り出します。
///
/// 祖先に[TWidget]が存在しない場合はエラーが出力されます。
TWidget scoped<TWidget extends ScopedWidgetBase>() {
TWidget ancestor<TWidget extends ScopedWidgetBase>() {
return ScopedWidgetScope.of(this);
}

@Deprecated("[scoped] is deprecated. Please use [ancestor] instead.")
TWidget scoped<TWidget extends ScopedWidgetBase>() => ancestor();
}
237 changes: 237 additions & 0 deletions packages/katana_scoped/lib/src/scoped_query.dart
@@ -0,0 +1,237 @@
part of katana_scoped;

/// {@template scoped_query}
/// [ScopedQuery] makes it possible to define values globally and manage state individually and safely.
///
/// Specify in [provider] a callback that returns the value you wish to manage.
///
/// Normally [hashCode] is used to manage state names, but if you want to specify a special name, specify [name].
///
/// [ScopedQuery]を利用してグローバルに値を定義して個別に安全に状態を管理することが可能になります。
///
/// [provider]に管理したい値を返すコールバックを指定してください。
///
/// 通常は[hashCode]を用いて状態の名前を管理しますが、特別に名前を指定したい場合は[name]を指定してください。
///
/// ```dart
/// final stateQuery = ScopedQuery(
/// () => "state",
/// );
///
/// class TestPage extends PageScopedWidget {
/// @override
/// Widget build(BuildContext context, PageRef ref) {
/// final state = ref.page.query(stateQuery);
///
/// return Scaffold(
/// body: Center(child: Text("${state}")), // `state`
/// );
/// }
/// }
/// ```
/// {@endtemplate}
@immutable
class ScopedQuery<Result> {
/// {@template scoped_query}
/// [ScopedQuery] makes it possible to define values globally and manage state individually and safely.
///
/// Specify in [provider] a callback that returns the value you wish to manage.
///
/// Normally [hashCode] is used to manage state names, but if you want to specify a special name, specify [name].
///
/// [ScopedQuery]を利用してグローバルに値を定義して個別に安全に状態を管理することが可能になります。
///
/// [provider]に管理したい値を返すコールバックを指定してください。
///
/// 通常は[hashCode]を用いて状態の名前を管理しますが、特別に名前を指定したい場合は[name]を指定してください。
///
/// ```dart
/// final stateQuery = ScopedQuery(
/// () => "state",
/// );
///
/// class TestPage extends PageScopedWidget {
/// @override
/// Widget build(BuildContext context, PageRef ref) {
/// final state = ref.page.query(stateQuery);
///
/// return Scaffold(
/// body: Center(child: Text("${state}")), // `state`
/// );
/// }
/// }
/// ```
/// {@endtemplate}
const ScopedQuery(
this.provider, {
String? name,
}) : _name = name;

final String? _name;

/// A callback that returns the value you want to manage.
///
/// 管理したい値を返すコールバック。
final Result Function() provider;

/// Returns `true` if the value is monitored for update notification.
///
/// 値を監視して更新通知を行う場合`true`を返します。
bool get listen => false;

/// Returns a callback that returns the value you want to manage.
///
/// 管理したい値を返すコールバックを返します。
Result Function() call() => provider;

/// Returns a name to identify the state.
///
/// Normally [hashCode] is used to manage state names, but if you want to specify a special name, specify [name].
///
/// 状態を識別するための名前を返します。
///
/// 通常は[hashCode]を用いて状態の名前を管理しますが、特別に名前を指定したい場合は[name]を指定してください。
String get name => _name ?? hashCode.toString();
}

/// {@template change_notifier_scoped_query}
/// [ChangeNotifierScopedQuery] makes it possible to define values globally and manage states individually and safely.
///
/// Only [ChangeNotifier] can be managed as a value. It is possible to monitor the status and send change notifications to the widget.
///
/// Specify in [provider] a callback that returns the value you wish to manage.
///
/// Normally [hashCode] is used to manage state names, but if you want to specify a special name, specify [name].
///
/// [ChangeNotifierScopedQuery]を利用してグローバルに値を定義して個別に安全に状態を管理することが可能になります。
///
/// [ChangeNotifier]のみを値として管理できます。状態を監視し、変更通知をウィジェットに送信することが可能です。
///
/// [provider]に管理したい値を返すコールバックを指定してください。
///
/// 通常は[hashCode]を用いて状態の名前を管理しますが、特別に名前を指定したい場合は[name]を指定してください。
///
/// ```dart
/// final valueNotifierQuery = ChangeNotifierScopedQuery(
/// () => ValueNotifier(0),
/// );
///
/// class TestPage extends PageScopedWidget {
/// @override
/// Widget build(BuildContext context, PageRef ref) {
/// final valueNotifier = ref.page.query(valueNotifierQuery);
///
/// return Scaffold(
/// body: Center(child: Text("${valueNotifier.value}")),
/// );
/// }
/// }
/// ```
/// {@endtemplate}
@immutable
class ChangeNotifierScopedQuery<Result extends Listenable?>
extends ScopedQuery<Result> {
/// {@template change_notifier_scoped_query}
/// [ChangeNotifierScopedQuery] makes it possible to define values globally and manage states individually and safely.
///
/// Only [ChangeNotifier] can be managed as a value. It is possible to monitor the status and send change notifications to the widget.
///
/// Specify in [provider] a callback that returns the value you wish to manage.
///
/// Normally [hashCode] is used to manage state names, but if you want to specify a special name, specify [name].
///
/// [ChangeNotifierScopedQuery]を利用してグローバルに値を定義して個別に安全に状態を管理することが可能になります。
///
/// [ChangeNotifier]のみを値として管理できます。状態を監視し、変更通知をウィジェットに送信することが可能です。
///
/// [provider]に管理したい値を返すコールバックを指定してください。
///
/// 通常は[hashCode]を用いて状態の名前を管理しますが、特別に名前を指定したい場合は[name]を指定してください。
///
/// ```dart
/// final valueNotifierQuery = ChangeNotifierScopedQuery(
/// () => ValueNotifier(0),
/// );
///
/// class TestPage extends PageScopedWidget {
/// @override
/// Widget build(BuildContext context, PageRef ref) {
/// final valueNotifier = ref.page.query(valueNotifierQuery);
///
/// return Scaffold(
/// body: Center(child: Text("${valueNotifier.value}")),
/// );
/// }
/// }
/// ```
/// {@endtemplate}
const ChangeNotifierScopedQuery(
super.provider, {
super.name,
});

@override
bool get listen => true;

@override
Result Function() call() => provider;
}

/// You can pass one parameter [ScopedQuery].
///
/// パラメーターを一つ渡すことができる[ScopedQuery]
///
/// {@macro scoped_query}
@immutable
class ScopedQueryFamily<Result, Param> {
/// You can pass one parameter [ScopedQuery].
///
/// パラメーターを一つ渡すことができる[ScopedQuery]
///
/// {@macro scoped_query}
const ScopedQueryFamily(
this.provider, {
String? name,
}) : _name = name;

final String? _name;

/// Returns a callback that returns the value you want to manage.
///
/// 管理したい値を返すコールバックを返します。
final Result Function(Param param) provider;

/// By passing [param], the corresponding [ScopedQuery] is returned.
///
/// [param]を渡すことで対応した[ScopedQuery]を返します。
ScopedQuery<Result> call(Param param) => ScopedQuery(
() => provider(param),
name: "${_name ?? hashCode}#${param.hashCode}",
);
}

/// You can pass one parameter [ChangeNotifierScopedQuery].
///
/// パラメーターを一つ渡すことができる[ChangeNotifierScopedQuery]
///
/// {@macro change_notifier_scoped_query}
@immutable
class ChangeNotifierScopedQueryFamily<Result extends Listenable?, Param>
extends ScopedQueryFamily<Result, Param> {
/// You can pass one parameter [ChangeNotifierScopedQuery].
///
/// パラメーターを一つ渡すことができる[ChangeNotifierScopedQuery]
///
/// {@macro change_notifier_scoped_query}
const ChangeNotifierScopedQueryFamily(
super.provider, {
super.name,
});

@override
ChangeNotifierScopedQuery<Result> call(Param param) =>
ChangeNotifierScopedQuery<Result>(
() => provider(param),
name: "${_name ?? hashCode}#${param.hashCode}",
);
}
89 changes: 89 additions & 0 deletions packages/katana_scoped/lib/value/query.dart
@@ -0,0 +1,89 @@
part of katana_scoped.value;

/// Provides an extension method for [Ref] to manage state using [ScopedQuery].
///
/// [ScopedQuery]を用いた状態管理を行うための[Ref]用の拡張メソッドを提供します。
extension RefQueryExtensions on Ref {
/// It is possible to manage the status by passing [query].
///
/// Defining [ScopedQuery] in a global scope allows you to manage state individually and safely.
///
/// [ScopedQuery] allows you to cache all values, while [ChangeNotifierScopedQuery] monitors values and notifies updates when they change.
///
/// [query]を渡して状態を管理することが可能です。
///
/// [ScopedQuery]をグローバルなスコープに定義しておくことで状態を個別に安全に管理することができます。
///
/// [ScopedQuery]を使うとすべての値をキャッシュすることができ、[ChangeNotifierScopedQuery]を使うと値を監視して変更時に更新通知を行います。
///
/// ```dart
/// final valueNotifierQuery = ChangeNotifierScopedQuery(
/// () => ValueNotifier(0),
/// );
///
/// class TestPage extends PageScopedWidget {
/// @override
/// Widget build(BuildContext context, PageRef ref) {
/// final valueNotifier = ref.page.query(valueNotifierQuery);
///
/// return Scaffold(
/// body: Center(child: Text("${valueNotifier.value}")),
/// );
/// }
/// }
/// ```
T query<T>(ScopedQuery<T> query) {
return getScopedValue<T, _QueryValue<T>>(
() => _QueryValue<T>(query: query),
listen: query.listen,
name: query.name,
);
}
}

@immutable
class _QueryValue<T> extends ScopedValue<T> {
const _QueryValue({
required this.query,
});

final ScopedQuery<T> query;

@override
ScopedValueState<T, ScopedValue<T>> createState() => _QueryValueState<T>();
}

class _QueryValueState<T> extends ScopedValueState<T, _QueryValue<T>> {
_QueryValueState();

late T _value;

@override
void initValue() {
super.initValue();
_value = value.query.call().call();
final val = _value;
if (val is Listenable) {
val.addListener(_handledOnUpdate);
}
}

void _handledOnUpdate() {
setState(() {});
}

@override
void dispose() {
super.dispose();
final val = _value;
if (val is Listenable) {
val.removeListener(_handledOnUpdate);
if (val is ChangeNotifier) {
val.dispose();
}
}
}

@override
T build() => _value;
}
1 change: 1 addition & 0 deletions packages/katana_scoped/lib/value/value.dart
Expand Up @@ -10,3 +10,4 @@ part 'fetch.dart';
part 'on.dart';
part 'refresh.dart';
part 'watch.dart';
part 'query.dart';

0 comments on commit cd6959a

Please sign in to comment.