Skip to content

Commit

Permalink
Merge pull request #2
Browse files Browse the repository at this point in the history
Release 0.2.0 ready
  • Loading branch information
shubham16g authored Jan 10, 2023
2 parents 61d7e0a + bebe4b1 commit 03e95c6
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 172 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 0.2.0
Builder, Consumer, and Listener Fixed
- Fixed Bug
- Fixed Library Name
- Fixed Repo URL
- Fixed Example Package Name
- Migrate ViewModelListener to StateFlowListener and SharedFlowListener
- Migrate ViewModelBuilder to StateFlowBuilder
- Migrate ViewModelConsumer to StateFlowConsumer

## 0.1.0

Initial version of the library
Expand Down
80 changes: 54 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
# View Model X

A ViewModel and Flow based state management package (inspired by Android ViewModel) make it easy to implement the MVVM pattern.
An Android similar state management package which helps to implement MVVM pattern easily.

> **Note:** In Android, ViewModel have an special functionality of keeping the state on configuration change.
> The ViewModel of this package is not for that as Flutter Project doesn't need it. It is only for separating the View Logic from UI.
## Features

- Simplified ☺️ State Management
- Similar code pattern with an Android project 🟰
- Easy for developers to migrate 🛩️ from Android to Flutter
- Allows developer to work with both Android to Flutter projects with ease 🐥
- Easy to implement MVVM pattern 💪
- Android 💚 like Environment
- StateFlow (equivalent to LiveData) ⛵
- SharedFlow 🌊

## Getting started

```console
flutter pub add view_model_x
```
## Package Components
- StateFlow, MutableStateFlow (equivalent to LiveData) ⛵
- SharedFlow, MutableSharedFlow 🌊
- ViewModel (to separate the view logic from UI like Cubit)
- ViewModelProvider
- StateFlowBuilder
- StateFlowConsumer
- StateFlowListener
- SharedFlowListener

## Usage

Expand Down Expand Up @@ -69,8 +76,8 @@ class CounterPageContent extends StatelessWidget {
return Scaffold(
appBar: AppBar(title: const Text('ViewModel Counter Example')),
body: Center(
// implement ViewModelBuilder to rebuild Text on StateFlow value changed/updated
child: ViewModelBuilder(
// implement StateFlowBuilder to rebuild Text on StateFlow value changed/updated
child: StateFlowBuilder(
// pass your StateFlow
stateFlow: context.vm<CounterViewModel>().counterStateFlow,
builder: (context, value) {
Expand All @@ -90,10 +97,10 @@ class CounterPageContent extends StatelessWidget {
}
```

## ViewModel Elements
## Package Components

### Custom ViewModel class
Create a your custom View-Model which must extends `ViewModel`. Declare all your Flows and View related logic inside of it.
### ViewModel (Create custom ViewModel class)
Create your custom View-Model which must extends `ViewModel`. Declare all your Flows and View related logic inside of it.
Don't forget to dispose all flows inside `dispose` method of `ViewModel`.

```dart
Expand Down Expand Up @@ -177,28 +184,28 @@ OR
context.vm<CustomViewModel>()
```

### ViewModelBuilder
### StateFlowBuilder

`ViewModelBuilder` is used to rebuild the widgets inside of it.
`StateFlowBuilder` is used to rebuild the widgets inside of it.
This requires `stateFlow` to listen on and `builder` to which rebuilds when the `stateFlow`'s value changed/updated.

```dart
ViewModelBuilder(
StateFlowBuilder(
stateFlow: context.vm<CustomViewModel>().myStateFlow, // pass StateFlow
builder: (context, value) {
return ChildWidget(value); // rebuild the widget with updated/changed value.
},
)
```

### ViewModelConsumer
### StateFlowConsumer

`ViewModelConsumer` is used to rebuild the widgets inside of it and call the listener.
`StateFlowConsumer` is used to rebuild the widgets inside of it and call the listener.
This requires `stateFlow` to listen on, `builder` and `listener`.
Whenever `stateFlow`'s value changed/updated, `builder` will rebuild the widgets inside of it and `listener` will called.

```dart
ViewModelConsumer(
StateFlowConsumer(
stateFlow: ViewModelProvider.of<CustomViewModel>(context).myStateFlow, // pass SharedFlow
listener: (context, value) {
// do stuff here based on value
Expand All @@ -209,22 +216,43 @@ ViewModelConsumer(
)
```

### ViewModelListener
### StateFlowListener

`ViewModelListener` is used to rebuild the widgets inside of it and call the listener.
This requires `flow` (which can be `SharedFlow` or `StateFlow`), `listener` and `child`.
Whenever `flow`'s value changed/updated (if it is StateFlow) or emit value (it it is StateFlow), `listener` will called.
`StateFlowListener` is used to catch the change/update value event of a `stateFlow`.
This requires `stateFlow`, `listener` and `child`.
Whenever `stateFlow`'s value changed/updated, `listener` will called.

```dart
ViewModelListener(
flow: ViewModelProvider.of<CustomViewModel>(context).anyStateFlowOrSharedFlow, // pass StateFlow or SharedFlow
StateFlowListener(
stateFlow: ViewModelProvider.of<CustomViewModel>(context).myStateFlow, // pass StateFlow
listener: (context, value) {
// do stuff here based on value
},
child: ChildWidget(),
)
```


### SharedFlowListener

`SharedFlowListener` is used to catch the emitted value from `sharedFlow`.
This requires `sharedFlow`, `listener` and `child`.
Whenever `sharedFlow` emits a value, `listener` will called.

```dart
SharedFlowListener(
sharedFlow: ViewModelProvider.of<CustomViewModel>(context).mySharedFlow, // pass SharedFlow
listener: (context, value) {
// do stuff here based on value
},
child: ChildWidget(),
)
```

## Coming soon
- MultiViewModelProvider
- MultiFlowListener

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ android {

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.viewmodelexample"
applicationId "com.example.viewmodelxexample"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
Expand Down
16 changes: 8 additions & 8 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:viewmodel/viewmodel.dart';
import 'package:view_model_x/view_model_x.dart';

void main() {
runApp(const MyApp());
Expand Down Expand Up @@ -85,18 +85,18 @@ class HomePageContent extends StatelessWidget {
icon: const Icon(Icons.mail_outline))
],
),
// implement ViewModelListener anywhere in code to listen any flow
body: ViewModelListener(
// pass your flow (StateFlow or SharedFlow)
flow: context.vm<MyViewModel>()._messageSharedFlow,
// implement SharedFlowListener anywhere in code to listen for emits from sharedFlow
body: SharedFlowListener(
// pass your SharedFlow
sharedFlow: context.vm<MyViewModel>().messageSharedFlow,
listener: (context, value) {
// get the emitted value. in this case <String?>"Hello from ViewModel!"
// get the emitted value. in this case <String>"Hello from ViewModel!"
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(value ?? "null message")));
.showSnackBar(SnackBar(content: Text(value)));
},
child: Center(
// implement ViewModelBuilder to rebuild Text on StateFlow value changed/updated
child: ViewModelBuilder(
child: StateFlowBuilder(
// pass your StateFlow
stateFlow: context.vm<MyViewModel>().counterStateFlow,
builder: (context, value) {
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
viewmodel:
view_model_x:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
version: "0.1.0"
sdks:
dart: ">=2.18.6 <3.0.0"
flutter: ">=1.17.0"
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies:
sdk: flutter


viewmodel:
view_model_x:
path: ../
cupertino_icons: ^1.0.2

Expand Down
17 changes: 6 additions & 11 deletions lib/src/flow.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import 'package:flutter/widgets.dart';

/// Abstract generic class which extends [ChangeNotifier] and stores value
abstract class AnyFlow<T> extends ChangeNotifier {
T _value;

AnyFlow(this._value);
}

/// [SharedFlow] is used to send data to the listeners.
class SharedFlow<T> extends AnyFlow<T?> {
SharedFlow() : super(null);
class SharedFlow<T> extends ChangeNotifier {
T? _value;

/// get the last emitted value
T? get lastEmitValue => _value;
Expand All @@ -25,11 +18,13 @@ class MutableSharedFlow<T> extends SharedFlow<T> {
}

/// [StateFlow] stores value and notify listeners whenever it changes.
class StateFlow<T> extends AnyFlow<T> {
class StateFlow<T> extends ChangeNotifier {
T _value;

/// get the current value.
T get value => _value;

StateFlow(T value) : super(value);
StateFlow(this._value);
}

/// [MutableStateFlow] is inherited from [StateFlow]. It can change/update the value.
Expand Down
61 changes: 61 additions & 0 deletions lib/src/shared_flow_listener.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:flutter/widgets.dart';
import 'flow.dart';

/// [SharedFlowListener] is used to catch the emitted value from [sharedFlow].
/// This requires [sharedFlow], [listener] and [child].
/// Whenever [sharedFlow] emits a value, [listener] will called.
class SharedFlowListener<T> extends StatefulWidget {
final SharedFlow<T> sharedFlow;
final void Function(BuildContext context, T value) listener;
final Widget child;

const SharedFlowListener(
{super.key,
required this.sharedFlow,
required this.listener,
required this.child});

@override
State<SharedFlowListener<T>> createState() => _SharedFlowListenerState<T>();
}

class _SharedFlowListenerState<T> extends State<SharedFlowListener<T>> {
@override
void initState() {
super.initState();
widget.sharedFlow.addListener(_stateListener);
}

@override
void dispose() {
widget.sharedFlow.removeListener(_stateListener);
super.dispose();
}

void _stateListener() {
if (mounted) {
final value = widget.sharedFlow.lastEmitValue;
if (value != null) {
widget.listener(context, value);
}
}
}

@override
void didUpdateWidget(covariant SharedFlowListener<T> oldWidget) {
if (widget.sharedFlow != oldWidget.sharedFlow) {
_migrate(widget.sharedFlow, oldWidget.sharedFlow, _stateListener);
}
super.didUpdateWidget(oldWidget);
}

void _migrate(Listenable a, Listenable b, void Function() listener) {
b.removeListener(listener);
a.addListener(listener);
}

@override
Widget build(BuildContext context) {
return widget.child;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import 'package:flutter/widgets.dart';

import 'flow.dart';

/// [ViewModelBuilder] is used to rebuild the widgets inside of it.
/// [StateFlowBuilder] is used to rebuild the widgets inside of it.
/// This requires [stateFlow] to listen on and [builder] to which rebuilds when the [stateFlow]'s value changed/updated.
class ViewModelBuilder<T> extends StatefulWidget {
class StateFlowBuilder<T> extends StatefulWidget {
final StateFlow<T> stateFlow;
final Widget Function(BuildContext context, T? value) builder;

const ViewModelBuilder(
const StateFlowBuilder(
{super.key, required this.stateFlow, required this.builder});

@override
State<ViewModelBuilder<T>> createState() => _ViewModelBuilderState<T>();
State<StateFlowBuilder<T>> createState() => _StateFlowBuilderState<T>();
}

class _ViewModelBuilderState<T> extends State<ViewModelBuilder<T>> {
class _StateFlowBuilderState<T> extends State<StateFlowBuilder<T>> {
@override
void initState() {
super.initState();
Expand All @@ -35,7 +35,7 @@ class _ViewModelBuilderState<T> extends State<ViewModelBuilder<T>> {
}

@override
void didUpdateWidget(covariant ViewModelBuilder<T> oldWidget) {
void didUpdateWidget(covariant StateFlowBuilder<T> oldWidget) {
if (widget.stateFlow != oldWidget.stateFlow) {
_migrate(widget.stateFlow, oldWidget.stateFlow, _stateListener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ import 'package:flutter/widgets.dart';

import 'flow.dart';

/// [ViewModelConsumer] is used to rebuild the widgets inside of it and call the listener.
/// [StateFlowConsumer] is used to rebuild the widgets inside of it and catch the event in [listener].
/// This requires [stateFlow] to listen on, [builder] and [listener].
/// Whenever [stateFlow]'s value changed/updated, [builder] will rebuild the widgets inside of it and [listener] will called.
class ViewModelConsumer<T> extends StatefulWidget {
class StateFlowConsumer<T> extends StatefulWidget {
final StateFlow<T> stateFlow;
final void Function(BuildContext context, T? value) listener;
final Widget Function(BuildContext context, T? value) builder;

const ViewModelConsumer(
const StateFlowConsumer(
{super.key,
required this.stateFlow,
required this.listener,
required this.builder});

@override
State<ViewModelConsumer<T>> createState() => _ViewModelConsumerState<T>();
State<StateFlowConsumer<T>> createState() => _StateFlowConsumerState<T>();
}

class _ViewModelConsumerState<T> extends State<ViewModelConsumer<T>> {
class _StateFlowConsumerState<T> extends State<StateFlowConsumer<T>> {
@override
void initState() {
super.initState();
Expand All @@ -41,7 +41,7 @@ class _ViewModelConsumerState<T> extends State<ViewModelConsumer<T>> {
}

@override
void didUpdateWidget(covariant ViewModelConsumer<T> oldWidget) {
void didUpdateWidget(covariant StateFlowConsumer<T> oldWidget) {
if (widget.stateFlow != oldWidget.stateFlow) {
_migrate(widget.stateFlow, oldWidget.stateFlow, _stateListener);
}
Expand Down
Loading

0 comments on commit 03e95c6

Please sign in to comment.