Skip to content

Commit

Permalink
Develop (#1)
Browse files Browse the repository at this point in the history
* Move tests to test/src.
Bump dartdoc version.
Add example implementations in readme for pub/sub.

* Run tests with flutter SDK.

* remove lcov conversion step and upload lcov.info directly

* Docs, test and codecov config.

* Release 0.0.1
  • Loading branch information
buijs-dev authored Apr 26, 2023
1 parent 7ab9fff commit c1650b0
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 127 deletions.
10 changes: 3 additions & 7 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,16 @@ jobs:

# Get dependencies
- name: Install dependencies
run: dart pub get
run: flutter pub get

# Run all tests with coverage
- name: Run tests with coverage
run: dart run test --coverage="coverage"

# Convert to LCOV
- name: Convert coverage to LCOV
run: dart pub run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --packages=.dart_tool/package_config.json --report-on=lib
run: flutter test --coverage

# Upload coverage data
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
env:
CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
with:
file: coverage.lcov
file: coverage/lcov.info
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 0.1.0
## 0.0.1
* Initial version containing:
* publisher widget
* subscriber widget
73 changes: 70 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,76 @@
[![](https://img.shields.io/badge/Buijs-Software-blue)](https://pub.dev/publishers/buijs.dev/packages)
[![GitHub license](https://img.shields.io/github/license/buijs-dev/klutter-dart?color=black&logoColor=black)](https://github.com/buijs-dev/klutter-dart/blob/main/LICENSE)

<br>
[![pub](https://img.shields.io/pub/v/klutter-ui)](https://pub.dev/packages/klutter-ui)
[![codecov](https://codecov.io/gh/buijs-dev/klutter-dart-ui/branch/main/graph/badge.svg?token=z0HCTKNLn5)](https://codecov.io/gh/buijs-dev/klutter-dart-ui)
[![CodeScene Code Health](https://codescene.io/projects/38075/status-badges/code-health)](https://codescene.io/projects/38075)

<img src="https://github.com/buijs-dev/klutter/blob/develop/.github/assets/metadata/icon/klutter_logo.png?raw=true" alt="buijs software logo" />

Flutter Widgets to be used in conjunction with the [klutter plugin](https://github.com/buijs-dev/klutter-dart).
Not yet published to pub...
Full support for:
- [MethodChannel (synchronous)](#MethodChannel)
- [EventChannel (asynchronous)](#EventChannel)

## MethodChannel
Example function which invokes method foo on the given channel and returns a String value.

```dart
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:klutter_ui/klutter_ui.dart';
const MethodChannel _channel =
MethodChannel('foo.bar.plugin/channel/my_simple_controller');
void foo({
State? state,
void Function(String)? onSuccess,
void Function(Exception)? onFailure,
}) =>
doEvent<String>(
state: state,
event: "foo",
channel: _channel,
onSuccess: onSuccess,
onFailure: onFailure,
);
```

Using the function as a tearoff requires just a single line of code:

```dart
TextButton(onPressed: foo, child: Text("Click"))
```

## EventChannel
Example implementation of a Subscriber (statefull widget) which subscribes to a channel and updates it state
everytime a new counter value is received.

```dart
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:klutter_ui/klutter_ui.dart';
const _stream = EventChannel('foo.bar.plugin/channel/counter');
class Counter extends Subscriber<int> {
const Counter({
required Widget Function(int?) child,
Key? key,
}) : super(
child: child,
channel: _stream,
topic: "counter",
key: key,
);
@override
int decode(dynamic json) => json as int;
}
```

All that is required to use the returned data is to wrap any widget with the Counter widget and then use it's value.

```dart
Counter(child: (res) => Text("$res")),
```
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignore:
- "lib/src/subscriber.dart"
6 changes: 3 additions & 3 deletions lib/src/adapter.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021 - 2022 Buijs Software
// Copyright (c) 2021 - 2023 Buijs Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand All @@ -18,7 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

/// Wrapper class for transfering response data from Platform to Flutter.
/// Wrapper class for transferring response data from Platform to Flutter.
///
/// Wraps an [exception] if calling the platform method has failed to be logged by the consumer.
/// Or wraps an [object] of type T when platform method has returned a response and
Expand All @@ -42,7 +42,7 @@ class AdapterResponse<T> {
/// Get a non-null object value.
T get object => _object!;

///Exception which occurred when calling a platform method failed.
/// Exception which occurred when calling a platform method failed.
Exception? _exception;

/// Set the exception value.
Expand Down
74 changes: 74 additions & 0 deletions lib/src/publisher.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,103 @@
// Copyright (c) 2021 - 2023 Buijs Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import "dart:async";

import "package:flutter/services.dart";
import "package:flutter/widgets.dart";

import "adapter.dart";

/// Send an event to Android/iOS platform and wait for a response [AdapterResponse].
///
/// Example implementation:
///
/// ```dart
/// const MethodChannel _channel =
/// MethodChannel('foo.bar.plugin/channel/my_simple_controller');
///
/// void foo({
/// State? state,
/// void Function(String)? onSuccess,
/// void Function(Exception)? onFailure,
/// }) =>
/// doEvent<String>(
/// state: state,
/// event: "foo",
/// channel: _channel,
/// onSuccess: onSuccess,
/// onFailure: onFailure,
/// );
/// ```
Future<AdapterResponse<OUT>> doEvent<OUT>({
/// Name of the event.
///
/// This name is used on the platform-side to determine which method to invoke.
required String event,

/// MethodChannel where the event is to be published.
required MethodChannel channel,

/// (Optional) Data to be send with the event.
///
/// E.g. if the method to be invoked requires parameters then add them as message.
dynamic message,

/// (Optional) State of widget from where the event is send.
///
/// Used to determine if the state is mounted.
/// If not then callbacks are not executed.
State? state,

/// (Optional) Decoding function used to decode the received response.
OUT Function(String)? decode,

/// (Optional) Encoding function used to encode message if is not
/// a StandardMessageCodec Type.
String Function(dynamic)? encode,

/// (Optional) Function to be executed if the event is processed successfully.
void Function(OUT)? onSuccess,

/// (Optional) Function to be executed if the event is processed unsuccessfully.
void Function(Exception)? onFailure,

/// (Optional) Function to be executed if the received response data is null.
void Function()? onNullValue,

/// (Optional) Function to be executed when the event is processed, regardless success (or NOT).
void Function(AdapterResponse<OUT>)? onComplete,
}) async {
/// Create a request message.
///
/// If [message] is null then [request] is also null.
final dynamic request = _toRequestMessage(message, encode);

/// Send the event and wait for a response.
final response = await _sendEvent(
sendRequest: () => channel.invokeMethod<dynamic>(event, request),
deserialize: decode,
);

/// Check if state is mounted.
///
/// if not then skip all callbacks and return the response.
if (state?.mounted ?? true) {
onComplete?.call(response);
if (response.isSuccess) {
Expand Down
71 changes: 66 additions & 5 deletions lib/src/subscriber.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,79 @@
import 'dart:async';
// Copyright (c) 2021 - 2023 Buijs Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import "dart:async";

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import "package:flutter/services.dart";
import "package:flutter/widgets.dart";

/// A widget that receives 0 or more messages of type [T]
/// and automatically updates it and it's dependents state.
///
/// Example implementation:
///
/// ```dart
/// const _stream = EventChannel('foo.bar.plugin/channel/counter');
///
/// class Counter extends Subscriber<int> {
/// const Counter({
/// required Widget Function(int?) child,
/// Key? key,
/// }) : super(
/// child: child,
/// channel: _stream,
/// topic: "counter",
/// key: key,
/// );
///
/// @override
/// int decode(dynamic json) => json as int;
/// }
/// ```
abstract class Subscriber<T> extends StatefulWidget {
/// Construct a new instance.
const Subscriber({
required this.child,
required this.channel,
required this.topic,
Key? key,
}) : super(key: key);

/// Any widget which wants access to the [T] data stream.
final Widget Function(T?) child;

/// Channel on which to subscribe.
final EventChannel channel;

/// Topic on which to subscribe.
///
/// Topic values are used to determine if data on a channel
/// is intended for this [Subscriber].
final String topic;

/// Decoding function used to decode data of Type [T].
///
/// If [T] is a StandardMessageCodec Type then a simple cast will suffice:
///
/// ```dart
/// @override
/// int decode(dynamic json) => json as int;
/// ```
T decode(dynamic json);

@override
Expand All @@ -25,7 +86,7 @@ class _SubscriberState<T> extends State<Subscriber<T>> {

void _start() {
_streamSubscription ??=
widget.channel.receiveBroadcastStream().listen(_update);
widget.channel.receiveBroadcastStream(widget.topic).listen(_update);
}

void _stop() {
Expand All @@ -36,7 +97,7 @@ class _SubscriberState<T> extends State<Subscriber<T>> {
}

void _update(dynamic data) {
setState(() => _data = widget.decode(data as String));
setState(() => _data = widget.decode(data));
}

@override
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: klutter_ui
description: Flutter widgets to be used in conjunction with the klutter plugin.
version: 0.1.0
version: 0.0.1
homepage: https://buijs.dev
repository: https://github.com/buijs-dev/klutter-dart-ui

Expand All @@ -13,5 +13,5 @@ dependencies:

dev_dependencies:
coverage: ^1.3.2
dartdoc: ^5.1.2
dartdoc: ^6.2.1
test: ^1.19.5
Loading

0 comments on commit c1650b0

Please sign in to comment.