I am using redux in my flutter code. I have a button which on press dispatches an action to call an async API (in my middleware). What I want to do is when the api call executes and gives back response, I want to show the snackbar stating it is successfully updated data or a error message. My issue is how to show the snackbar by dispatching an action to show it. Or is there a better way to do this in redux?
Disclaimer:
This is not code written in an IDE and run.. it is just a way to describe a solution to your question. There may be mistakes.
Assuming you have the Redux store and using async for the API call.
Your page widget:
...
_onSubmit(Store<MyState> store) {
store.dispatch(new SubmitAction(onCompleted: _onCompleted, onError: _onError))
}
_onCompleted() {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("Item Completed")));
}
_onError(error) {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("Error Occurred")));
}
Your Redux middleware:
apiMiddleware(Store<MyState> store, action, NextDispatcher next) {
if(action is SubmitAction) {
yourAPICall()
.then((data) => action.onCompleted())
.catchError((e) => action.onError(error));
}
next(action);
}
MyAction class:
typedef void VoidCallback();
typedef void ErrorCallback(error);
class MyAction {
MyAction({this.onCompleted, this.onError})
VoidCallback onCompeleted;
ErrorCallback onError;
}
Some of this could be done with futures but this is simple enough.
Related
I have a facade function that reloads the current firebase user and returns it. The thing is that the user reloading part has a timeout and it needs to be tested.
Function:
Future<Option<User>> getSignedInUser() async {
// Reload currentUser if possible
// it mustn't throw [TimeoutException] for whole function,
// this is what this try/catch does
try {
await reloadCurrentUser().timeout(const Duration(seconds: 20));
} catch (e) {
log(e.toString(), name: TAG);
}
return optionOf(_auth.currentUser);
}
reloadCurrentUser() function:
Future<Either<AuthFailure, Unit>> reloadCurrentUser() async {
try {
await _auth.currentUser?.reload();
return right(unit);
} catch (e) {
log(e.toString(), name: TAG);
return left(const AuthFailure.userReloadingError());
}
}
The question is how to test reloadCurrentUser() timeout? I'm trying to throw a TimeoutException when this function is called, but then it throws an error for the whole test.
Current Test function:
test(
'Reaches timeout when reloading currentUser, '
'throws TimeoutException, but function continues '
'and returns optionOf currentUser', () async {
reset(fakeFirebaseAuth);
reset(fakeFacebookAuth);
reset(fakeGoogleSignIn);
final currentUser = FakeUser();
// It says that currentUser exists and *IS* authenticated
when(() => fakeFirebaseAuth.currentUser).thenReturn(currentUser);
when(() => firebaseAuthFacade.reloadCurrentUser())
.thenThrow(TimeoutException('timeout', const Duration(seconds: 20)));
final result = await firebaseAuthFacade.getSignedInUser();
expect(result, isA<Some<User>>());
});
Maybe it's better to remove timeout and use some connectivity package to ensure that the user has a network connection and only then reload the current user?
For testing I'm using mocktail package.
You can use the fake_async package.
Here's a simple example from their docs that you can modify for your use case:
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';
void main() {
test("Future.timeout() throws an error once the timeout is up", () {
// Any code run within [fakeAsync] is run within the context of the
// [FakeAsync] object passed to the callback.
fakeAsync((async) {
// All asynchronous features that rely on timing are automatically
// controlled by [fakeAsync].
expect(Completer().future.timeout(Duration(seconds: 5)),
throwsA(isA<TimeoutException>()));
// This will cause the timeout above to fire immediately, without waiting
// 5 seconds of real time.
async.elapse(Duration(seconds: 5));
});
});
}
I want to connect firebase stuff. What I'm trying in my news feed is when I press addTofavourite its name should go to firebase. So in my code for fetch, I used componentDidMount and for sending too there is componentDidMount. I have no idea how to connect them.
This is what I tried, but it's not working.
componentDidMount() {
firebase.auth().signInWithEmailAndPassword
("web#imandy.ie", "123456")
//////////////////////////////////////
this.fetchNews();
}
for fetching news
componentDidMount() {
this.fetchNews();
}
for firebase thing
componentDidMount() {
firebase.auth().signInWithEmailAndPassword("web#imandy.ie", "123456" )
}
Please try like this
componentDidMount = async () => {
const { user } = await firebase.auth().signInWithEmailAndPassword("web#imandy.ie", "123456");
// If you want to use user detail, write code here
// ...
this.fetchNews();
}
you have to call this function in componentWillReciveProps(){} or componentDidUpdate(){}
I'm completely new to redux and it's beginning to make sense. I'm trying to use the middleware to keep the reducer a pure function but it's giving me an error I don't understand. I'm following the flutter architecture samples for redux
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState.loading(),
middleware: createStoreFlashCardsMiddleware(),
);
runApp(new MyApp(store));
}
//the middleware
List<Middleware<AppState>> createStoreFlashCardsMiddleware() {
final loadFlashCards = _createLoadFlashCardsMiddleware();
final saveFlashCards = _createSaveFlashCardsMiddleWare();
return [
TypedMiddleware<AppState, FetchFlashCardsAction>(loadFlashCards),
TypedMiddleware<AppState, AddFlashCardAction>(saveFlashCards),
TypedMiddleware<AppState, ClearCompletedAction>(saveFlashCards),
TypedMiddleware<AppState, ToggleAllAction>(saveFlashCards),
TypedMiddleware<AppState, UpdateFlashCardAction>(saveFlashCards),
TypedMiddleware<AppState, FetchCardsSucceededAction>(saveFlashCards),
TypedMiddleware<AppState, DeleteFlashCardAction>(saveFlashCards),
];
}
Middleware<AppState> _createSaveFlashCardsMiddleWare() {
return (Store store, action, NextDispatcher next) async {
// YOUR LOGIC HERE
// After you do whatever logic you need to do,
// call this Redux built-in method,
// It continues the redux cycle.
next(action);
};
}
Middleware<AppState> _createLoadFlashCardsMiddleware() {
return (Store store, action, NextDispatcher next) async {
next(action);
};
}
The error is:
error: The argument type 'List<(Store<AppState>, dynamic, (dynamic) → void) → void> (C:\flutter\bin\cache\pkg\sky_engine\lib\core\list.dart)' can't be assigned to
the parameter type 'List<(Store<AppState>, dynamic, (dynamic) → void) → void> (C:\flutter\bin\cache\pkg\sky_engine\lib\core\list.dart)'. (argument_type_not_assignable at [line])
I want to use only React, React Redux, React Router and Redux Thunk.
I want to navigate to a dashboard page when a successful user creation action is dispatched. Here is my async action creator,
export function createUser() {
return dispatch => {
dispatch(creatingUser());
return axios.post('/api/users').then(() => {
// how to navigate to dashboard after this action is dispatched?
dispatch(createdUser());
});
};
}
Can you show me exactly where is the place I should naviage programmatically?
Initially looking, I would hope that "createdUser" returns a promise (like #idbehold asked previously)
in a nutshell, something like this.
// make sure your function createdUser returns a promise
function createdUser() {
return new Promise((resolve, reject) => {
//simulate some api request
setTimeout( () =>{
// api resolves. in the .then() do:
resolve()
}, 4000)
})
}
// the dispatch will forward resolution of the promise ahead into the
// .then.. then you can redirect.
dispatch(createdUser()).then( ()=> {
console.log("NAVIGATING AWAY")
//browserHistory.push('/some/path')
//assuming you are importing browserHistory
})
I hope I was helpful, if not :-( , perhaps I didn't fully understand what your need is/was. Let me know, and I'll try to help further.
I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).
What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.
The async logic relying on redux-thunk middleware and q:
// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
...
}
function getAsync(date) {
return function (dispatch) {
return goGetAPIUrl(date).then(
val => dispatch(gotURL(val)),
error => dispatch(apologize(error))
);
};
}
export default function eventuallyGetAsync(event, date) {
if(event.which == 37...) {
return getAsync(date);
} else {
return {
type: "NOACTION"
}
}
}
Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
actions.eventuallyGetAsync(event, gridAppState.date);
});
}
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
}
App.propTypes = {
actions: PropTypes.object.isRequired,
gridAppState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.
What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?
You should handle the event in your component and call the correct action depending on the key pressed.
So when you dispatch an async action you can do something like
export default function getNextPhoto(currentDate) {
return (dispatch) => {
const newDate = calculateNewDate(currentDate);
dispatch(requestNewPhoto(newDate));
return photosService.getPhotoOfDate(newDate)
.then((response) => {
dispatch(newPhotoReceived(response.photoURL);
});
};
}
You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.
Your App would look like
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
if (event.which == 37) {
actions.getNextPhoto(gridAppState.date);
} else if (...) {
actions.getPrevPhoto(gridAppState.date);
}
// etc
});
}
}
By the way you re still missing your reducers that update your state in the Redux Store.