Giter Site home page Giter Site logo

michaelmarner / dart-redux-remote-devtools Goto Github PK

View Code? Open in Web Editor NEW
52.0 5.0 10.0 7.52 MB

Remote Devtools for Dart & Flutter

Home Page: https://pub.dartlang.org/packages/redux_remote_devtools

License: MIT License

Dart 94.48% Shell 1.76% Java 1.24% Objective-C 2.52%
dart flutter redux devtools hacktoberfest

dart-redux-remote-devtools's Introduction

Redux Remote Devtools for Dart and Flutter

Build Status Coverage Status

Redux Remote Devtools support for Dart and Flutter.

Devtools Demo

Installation

Add the library to pubspec.yaml:

dependencies:
  redux_remote_devtools: ^2.0.0

Middleware configuration

Add the middleware to your Redux configuration:

  var remoteDevtools = RemoteDevToolsMiddleware('192.168.1.52:8000');
  final store = new DevToolsStore<AppState>(searchReducer,
      middleware: [
        remoteDevtools,
      ]);
  remoteDevtools.store = store;
  await remoteDevtools.connect();

⚠️ Using mutliple middleware?

If you use other middleware, RemoteDevTools must be put last. Otherwise, actions and state updates will be out of sync

What's going on here?

  1. Create a new instance of the devtools middleware. Specify the host and port to connect to.

  2. Wait for devtools to connect to the remotedev server

  3. Initialise your store. To take advantage of time travel, you should use a DevToolsStore. Pass in remoteDevTools with the rest of your middlware

  4. The middleware needs a reference to the store you just created, so commands from devtools can be dispatched. So as a final step, set the reference.

Using remotedev

Use the Javascript Remote Devtools package. Start the remotedev server on your machine

npm install -g remotedev-server
remotedev --port 8000

Run your application. It will connect to the remotedev server. You can now debug your redux application by opening up http://localhost:8000 in a web browser.

Encoding Actions and State

In the Javascript world, Redux follows a convention that your redux state is a plain Javascript Object, and actions are also Javascript objects that have a type property. The JS Redux Devtools expect this. However, Redux.dart tries to take advantage of the strong typing available in Dart. To make Redux.dart work with the JS devtools, we need to convert actions and state instances to JSON before sending.

Remember that the primary reason for using devtools is to allow the developer to reason about what the app is doing. Therefore, exact conversion is not strictly necessary - it's more important for what appears in devtools to be meaningful to the developer.

Enconding Strategy for Actions

We use the class name as the action type for class based actions. For enum typed actions, we use the value of the action. For example:

enum EnumActions {
  Action1;
  Action2;
}

class ClassAction {}

When converted, these actions will be {"type": "Action1"} or {"type": "ClassAction"}, etc.

We also want to send the action properties over to devtools. To do this, we attempt to jsonEncode the entire Action, and attach this JSON data as a payload property. For example:

class ClassAction {
  int someValue;

  toJson() {
    return {'someValue': this.someValue};
  }
}

Would appear in devtools as:

{
  "type": "ClassAction",
  "payload": {
    "someValue": 5 // or whatever someValue was set to
  },
}

This of course means your Actions need to be json encodable. You can do what the example above does and write your own toJson method. However, a better approach is to use a generator like json_serializable to do it for you. If your action is not json encodable, the payload property will be missing in devtools.

Encoding strategy for State

For state, we simply attempt to jsonEncode the entire thing. If your state cannot be converted, then state updates will not appear in devtools.

Overriding these strategies

The strategy described above should work for most cases. However, if you want to do something different, you can replace the ActionEncoder and StateEncoder with your own implementations:

  var remoteDevtools = RemoteDevToolsMiddleware('192.168.1.52:8000', actionEncoder: MyCoolActionEncoder);

Making your actions and state json encodable

You can either write your own toJson methods for each of your actions and your state class. However, this quickly becomes cumbersome and error prone. Instead, the recommended way is to make use of the json_annotation package to automatically generate toJson functions for you.

Dispatching Actions from DevTools

You are able to dispatch actions from the Devtools UI and have these processed by the redux implementation in your Flutter app.

In order for this to work, you need to implement an ActionDecoder. ActionDecoder's job is to take the JSON data received from the Devtools UI, and return an action that your reducers know how to use. For example if we dispatch an action:

{
  "type": "INCREMENT"
}

We would implement an ActionDecoder like so:

ActionDecoder myDecoder = (dynamic payload) {
  final map = payload as Map<String, dynamic>;
  if (map['type'] == 'INCREMENT') {
    return IncrementAction();
  }
};

Essentially, you need to map every JSON action type into an action that can be used by your reducers.

Please get in touch if you have any awesome ideas for how to make this process smoother.

Example Apps

This repo includes remote-devtools enabled versions of the flutter-redux example apps:

  • flutter-redux Github Search App.

    • Demonstrates how class based actions and nested state objects are serialised and made browseable in devtools

    • Demonstrates the limits of time travel in apps that use epics

dart-redux-remote-devtools's People

Contributors

artyhedgehog avatar kealjones-wk avatar michaelmarner avatar nimrodda avatar sidrao2006 avatar tvolkert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

dart-redux-remote-devtools's Issues

connect to dev tools hangs

My environment setup is:

  • macOS Catalina
  • iOS Simulator
  • redux_remote_devtools: 1.0.2
  • redux_dev_tools: 0.5.2
  • redux: 4.0.0

I use the following code to setup my store:

import 'dart:developer';

import 'package:mplan/epics/weekplan_epics.dart';
import 'package:mplan/login/auth_epics.dart';
import 'package:mplan/model/app_state.dart';
import 'package:mplan/reducers/app_reducer.dart';
import 'package:redux/redux.dart';
import 'package:redux_dev_tools/redux_dev_tools.dart';
import 'package:redux_epics/redux_epics.dart';
import 'package:redux_logging/redux_logging.dart';
import 'package:redux_remote_devtools/redux_remote_devtools.dart';

Future<Store<AppState>> setupStore() async {
  log('setup');
  // TOOD add switch to disable devtools for production build
  final remoteDevtools = RemoteDevToolsMiddleware('192.168.1.106:8000');
  final epic = combineEpics<AppState>([
    loginEpic,
    logoutEpic,
    defaultGroupEpic,
    loadDayPlansEpic,
  ]);
  final epicMiddleware = EpicMiddleware(epic);
  // final store = Store<AppState>(
  final store = DevToolsStore<AppState>(
    appReducer,
    initialState: AppState(),
    middleware: [
      // LoggingMiddleware.printer(),
      epicMiddleware,
      remoteDevtools,
    ],
  );

  remoteDevtools.store = store;
  log('before connect');
  await remoteDevtools.connect();
  log('after connect');

  return store;
}

I start the server with:

remotedev --port 8000

when starting the app on the iOS simulator I only get the following output (please note the log(...)-call in the store setup code):

Launching lib/main.dart on iPhone 11 Pro Max in debug mode...
Xcode build done.                                           30.6s
	path: satisfied (Path is satisfied), interface: en0
Configuring the default Firebase app...
	path: satisfied (Path is satisfied), interface: en0
Configured the default Firebase app __FIRAPP_DEFAULT.
[log] setup
[log] before connect

after a couple of minutes, the following output also comes along:

	[C1.1 E305D8AA-0FCD-4691-92D7-553C1EA8E75C 2a02:120b:c3ee:9890:80f6:5fac:e4ad:a5b8.50478<->2a00:1450:400a:800::200a.443]
	Connected Path: satisfied (Path is satisfied), interface: en0
	Duration: 239.973s, DNS @0.002s took 0.002s, TCP @0.006s took 0.015s, TLS took 0.043s
	bytes in/out: 4487/1485, packets in/out: 8/10, rtt: 0.013s, retransmitted packets: 0, out-of-order packets: 0

so there is no after connect and screen in the iOS simulator will just stay white.
The IP in the code is correct, but I also get the same behaviour when I use localhost:8000
Is there any way I can debug this in more details?

when I remove the remote dev tools and just use the alternative LoggingMiddleware.printer(), everything works fine.

Random port is used.

Hey guys, before I create this issue, yes I have already explored and I am aware that this is duplicated from issue #36, but anyway I decided to create another one, apparently it is already stopped, so the problem is this, I have already made the modifications on my current project and apparently it doesn't seem to work, so I decided to use your examples and still I have the same problem, I am available to debug or log whatever.

I also checked to see if there were any of these errors in the SocketCluster Dart Client library, and still no solution. Then later I noticed that it uses a different connection URL, and even then I tested it, and it seems to only recognize "ws" as the IP.

Any more information that is needed to clarify I am here. Any help or attempts are welcome and I appreciate it.

Dispatched action are visible but state is undefined

I can see the dispatched action, but state is always undignified.

I connect to the store in the main fuction:

var remoteDevtools = RemoteDevToolsMiddleware('192.168.0.105:8000');
  await remoteDevtools.connect();
  final store = DevToolsStore<AppState>(appStateReducer,
      initialState: AppState.initialState(),
      middleware: [remoteDevtools, appStateMiddleWare]);
  remoteDevtools.store = store;

and my reducer looks like

AppState appStateReducer(AppState state, action) {
  return AppState(
    restaurant: restaurantReducer(state.restaurant, action),
    categories: categoriesReducer(state.categories, action),
    products: productsReducer(state.products, action),
    locale: localeReducer(state.locale, action),
    cart: cartReducer(state.cart, action),
    subTotal: subTotalReducer(state, action),
    loading: loadingReducer(state.loading, action),
  );
}

and the reducer is divided into several TypedReducers e.g:

Map<int, CartItem> increaseCartItemCount(Map<int, CartItem> cart, int id) {
  Map<int, CartItem> newCart = {}..addAll(cart);
  CartItem newCartItem = cart[id].copyWith(count: cart[id].count + 1);
  newCart[id] = newCartItem;
  return newCart;
}

Map<int, CartItem> decreaseCartItemCount(Map<int, CartItem> cart, int id) {
  Map<int, CartItem> newCart = {}..addAll(cart);
  CartItem newCartItem = cart[id].copyWith(count: cart[id].count - 1);
  newCart[id] = newCartItem;
  if (newCart[id].count <= 0) newCart.remove(id);
  return newCart;
}

Map<int, CartItem> addProductToCartReducer(
    Map<int, CartItem> prevCart, action) {
  Map<int, CartItem> cart = prevCart;
  if (cart.containsKey(action.product.id)) {
    cart = increaseCartItemCount(cart, action.product.id);
  } else {
    Map<int, CartItem> newCart = {}..addAll(cart);
    newCart[action.product.id] = CartItem(
        1,
        action.product.id,
        action.product.nameEn,
        action.product.nameAr,
        action.product.imageUrl,
        action.product.price,
        action.product.calories,
        action.product.caloriesUnit);
    cart = newCart;
  }
  return cart;
}

Map<int, CartItem> increaseItemCountReducer(
    Map<int, CartItem> prevCart, action) {
  Map<int, CartItem> cart = prevCart;
  return increaseCartItemCount(cart, action.productId);
}

Map<int, CartItem> decreaseItemCountReducer(
    Map<int, CartItem> prevCart, action) {
  Map<int, CartItem> cart = prevCart;
  return decreaseCartItemCount(cart, action.productId);
}

Reducer<Map<int, CartItem>> cartReducer = combineReducers<Map<int, CartItem>>([
  TypedReducer<Map<int, CartItem>, AddProductToCartAction>(
      addProductToCartReducer),
  TypedReducer<Map<int, CartItem>, IncreaseItemCountAction>(
      increaseItemCountReducer),
  TypedReducer<Map<int, CartItem>, DecreaseItemCountAction>(
      decreaseItemCountReducer),
]);

and I'm using

Dart VM version: 2.2.0 (Tue Feb 26 15:04:32 2019 +0100) on "windows_x64"

and fluttrer

Flutter 1.2.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 8661d8aecd (6 weeks ago) • 2019-02-14 19:19:53 -0800
Engine • revision 3757390fa4
Tools • Dart 2.1.2 (build 2.1.2-dev.0.0 0a7dcf17eb)

Add a note to the README that RemoteDevToolsMiddleware should be last

I was seeing problems where Actions were listed out of order and State changes being reported as part of the wrong Action so I went looking for a logging middleware I could compare with.

I found redux_logging | Dart Package and noticed they said:

Note: The LoggingMiddleware needs be the LAST middleware in the list.

I moved my RemoteDevToolsMiddleware to the end of the middleware list and the problems were solved.

Is this a known issue? I can create a minimal repro if you'd like. Thanks!

Send current state on connect

Nothing is sent to remotedev-server until an action has been dispatched after connecting. Let's send the current state on connect.

Move to redux-devtools-cli

I imagine you're well aware the RemoteDev Server package is no longer being updated, was renamed to redux-devtools-cli and merged into redux-devtools monorepo.

I found redux-devtools-cli wasn't working and apparently it was because redux-devtools-core was not published but that was recently fixed and redux-devtools-cli does appear to be working for me (at least in the sense that the UI will load in a either a browser or atom window).

I started a doco PR hoping the redux-devtools-cli would "just work" but my app doesn't seem to connect and I can't find an error message anywhere. I'm thinking I'll put the PR up in case it's useful and if I find some time see if I can figure out what's going wrong. I don't really know what I'm doing so I don't necessarily expect to get anywhere but I'd like to see if I can.

Do you have any plans to change to using the redux-devtools-cli? Thanks!

Implement null safety

Now that Dart's null safety is in Flutter beta, probably time to look at converting this library to work with it.

Replace ActionEncoder, ActionDecoder, and StateEncoder with functions

From the Dart styleguide:

AVOID defining a one-member abstract class when a simple function will do.

http://dart-lang.github.io/linter/lints/one_member_abstracts.html

ActionEncoder, ActionDecoder, and StateDecoder are all abstract classes with a single abstract method. These should be replaced with function typedefs.

This will be a breaking change, should consider moving to strict semver for versioning and releasing this as 1.0.0

Try to reconnect to devtools server

This is most likely an issue with socketcluster_client, but putting it here for reference. Add ability for the middleware to automatically attempt to reconnect to remote devtools.

flutter: Unknown type:START

In the app console I receive: flutter: Unknown type:START.

Just by looking at the network traffic it seems that the event doesn't do anything relevant and just indicates that the communication has started. It isn't critical, but it would be nice to have a message indicating that's everything is setup correctly instead of Unknown type:START ;)

Add support for "commit" action

The "commit" button in redux dev tools usually clears the action list and commits state.

image

However when clicking with Flutter we get "Unknown commans (sic): COMMIT. Ignoring"

image

Random port used

Hi,

I tried the example as mentionned on pub.dev but the app runs in timeout because it cannot connect to remote devtools and the used port is not the one I provided. Is this normal?
image

Thank you

'Future already completed' exception on iOS and macOS

I've started getting this exception recently

  • occurs during app load
  • doesn't appear to stop remote-devtools working

[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Bad state: Future already completed

Stack Trace
#0      _AsyncCompleter.complete  (dart:async/future_impl.dart:43:31)
#1      RemoteDevToolsMiddleware._waitForStart.<anonymous closure>> package:redux_remote_devtools/src/remote_devtools_middleware.dart:70
#2      Emitter.handleEmit package:socketcluster_client/src/emitter.dart:50
#3      Socket.handleMessage package:socketcluster_client/src/socket.dart:171
#4      _rootRunUnary  (dart:async/zone.dart:1198:47)
#5      _CustomZone.runUnary  (dart:async/zone.dart:1100:19)
#6      _CustomZone.runUnaryGuarded  (dart:async/zone.dart:1005:7)
#7      _BufferingStreamSubscription._sendData  (dart:async/stream_impl.dart:357:11)
#8      _BufferingStreamSubscription._add  (dart:async/stream_impl.dart:285:7)
#9      _SyncStreamControllerDispatch._sendData  (dart:async/stream_controller.dart:808:19)
#10     _StreamController._add  (dart:async/stream_controller.dart:682:7)
#11     _StreamController.add  (dart:async/stream_controller.dart:624:5)
#12     new _WebSocketImpl._fromSocket.<anonymous closure>  (dart:_http/websocket_impl.dart:1145:21)
#13     _rootRunUnary  (dart:async/zone.dart:1198:47)
#14     _CustomZone.runUnary  (dart:async/zone.dart:1100:19)
#15     _CustomZone.runUnaryGuarded  (dart:async/zone.dart:1005:7)
#16     _BufferingStreamSubscription._sendData  (dart:async/stream_impl.dart:357:11)
#17     _BufferingStreamSubscription._add  (dart:async/stream_impl.dart:285:7)
#18     _SinkTransformerStreamSubscription._add  (dart:async/stream_transformers.dart:69:11)
#19     _EventSinkWrapper.add  (dart:async/stream_transformers.dart:15:11)
#20     _WebSocketProtocolTransformer._messageFrameEnd  (dart:_http/websocket_impl.dart:338:23)
#21     _WebSocketProtocolTransformer.add  (dart:_http/websocket_impl.dart:232:46)
#22     _SinkTransformerStreamSubscription._handleData  (dart:async/stream_transformers.dart:121:24)
#23     _rootRunUnary  (dart:async/zone.dart:1198:47)
#24     _CustomZone.runUnary  (dart:async/zone.dart:1100:19)
#25     _CustomZone.runUnaryGuarded  (dart:async/zone.dart:1005:7)
#26     _BufferingStreamSubscription._sendData  (dart:async/stream_impl.dart:357:11)
#27     _BufferingStreamSubscription._add  (dart:async/stream_impl.dart:285:7)
#28     _SyncStreamControllerDispatch._sendData  (dart:async/stream_controller.dart:808:19)
#29     _StreamController._add  (dart:async/stream_controller.dart:682:7)
#30     _StreamController.add  (dart:async/stream_controller.dart:624:5)
#31     _Socket._onData  (dart:io-patch/socket_patch.dart:2020:41)
#32     _rootRunUnary  (dart:async/zone.dart:1206:13)
#33     _CustomZone.runUnary  (dart:async/zone.dart:1100:19)
#34     _CustomZone.runUnaryGuarded  (dart:async/zone.dart:1005:7)
#35     _BufferingStreamSubscription._sendData  (dart:async/stream_impl.dart:357:11)
#36     _BufferingStreamSubscription._add  (dart:async/stream_impl.dart:285:7)
#37     _SyncStreamControllerDispatch._sendData  (dart:async/stream_controller.dart:808:19)
#38     _StreamController._add  (dart:async/stream_controller.dart:682:7)
#39     _StreamController.add  (dart:async/stream_controller.dart:624:5)
#40     new _RawSocket.<anonymous closure>  (dart:io-patch/socket_patch.dart:1556:33)
#41     _NativeSocket.issueReadEvent.issue  (dart:io-patch/socket_patch.dart:1052:14)
#42     _microtaskLoop  (dart:async/schedule_microtask.dart:41:21)
#43     _startMicrotaskLoop  (dart:async/schedule_microtask.dart:50:5)

I made a minimal repro: https://github.com/nickmeinhold/test_rdt, it's just the starter project with the redux_remote_devtools package and

final remoteDevtools = RemoteDevToolsMiddleware('localhost:8000');
await remoteDevtools.connect();

added to main.dart

Thanks!

RFC: Anybody using this for web?

I'm only really interested in Flutter for mobile, and not web. That said, this library could be useful for people using Flutter for web.

I'm interested in finding out how this works for web, how it can be improved, what the pain points are etc.

So, anybody using this for web?

Support for ACTION events

Hi again,

it seems that dispatching an action from the dev tools app isn't supported. Do you plan to add it?

On web, If dev tools server can't be reached, the Flutter app hard crashes

We have the connect wrapped in a try block, but even so, if the server can't be reached in a local web environment, the flutter app will crash and never display the app.

  /// Connect to RemoteDev Tools
  Future<void> connect() async {
    try {
      await _remoteDevTools.connect();
    } catch (e) {
      print('RemoteDevTools unreachable $e');
    }
  }

Error in Chrome console

html_dart2js.dart:31720 WebSocket connection to 'ws://localhost:8000/socketcluster/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

Is it possible to get a little gentler error handling in redux_remote_devtools?

Version Solving Failed

Trying to install dependency I get this error:

The current Dart SDK version is 3.2.5.

Because redux_remote_devtools >=3.0.0 depends on redux_dev_tools ^0.7.0 and redux_remote_devtools <3.0.0 doesn't support null safety,
every version of redux_remote_devtools requires redux_dev_tools ^0.7.0.
So, because redux_dev_tools depends on redux_remote_devtools any and redux_dev_tools is 0.1.0, version solving failed.

The lower bound of "sdk: '>=2.0.0 <3.0.0'" must be 2.12.0 or higher to enable null safety.
For details, see https://dart.dev/null-safety

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.