I am looking around on how to check if network or GPS are enabled on a device when my application starts. And if they are disabled prompt the user to enable it.
Is this posible in React-native?, and is there any class or tool that could help with this type of dialogs?
So I am going to start answering my own question.
For GPS:
there seems to be a sensible solution. IOS seems to natively request if there is a geolocation request. And for Android this is not natively supported but someone has created a module for that ( https://github.com/webyonet/react-native-android-location-services-dialog-box )
so in my action creator I added the next code:
if(Platform.OS === 'android')
LocationServicesDialogBox.checkLocationServicesIsEnabled({
message: "<h2>Use Location?</h2> \
This app wants to change your device settings:<br/><br/>\
Use GPS for location<br/><br/>",
ok: "YES",
cancel: "NO"
}).then(() => {
locationTracking(dispatch, getState, geolocationSettings)
})
For Network:
There is no native support for neither so i end up doing my own action creator to check.
export function networkCheck(){
return (dispatch) => {
const dispatchNetworkState = (isConnected) => dispatch({
type: types.NETWORK_STATE,
state: isConnected
})
const handle = () => NetInfo.isConnected.fetch().done(dispatchNetworkState)
NetInfo.isConnected.addEventListener('change', handle);
}
}
A little extra:
for GPS i added this to check if the user goes and disable GPS on the middle of the task.
export function locationCheck(geolocationSettings = {enableHighAccuracy: true, timeout: 20000, maximumAge: 10000, distanceFilter:10}){
return (dispatch) => {
navigator.geolocation.watchPosition(
() => {
dispatch({
type: types.LOCATION_STATE,
state: true
})
},
() => {
dispatch({
type: types.LOCATION_STATE,
state: false
})
},
geolocationSettings)
}
}
For enabling location/gps on Android I can recommend this module: https://github.com/Richou/react-native-android-location-enabler
It is using the standard Android dialog for location:
If you use any kind of package to get the user's location, most likely they will automatically ask for the user to enable the hardware part when you request it for the first time.
So, you can do something like this to show the prompt:
CheckForGPSEnablement() {
return new Promise(resolve => {
RNLocation.configure({
distanceFilter: 100, // Meters
desiredAccuracy: {
ios: "best",
android: "balancedPowerAccuracy",
},
// Android only
androidProvider: "auto",
interval: 5000, // Milliseconds
fastestInterval: 10000, // Milliseconds
maxWaitTime: 5000, // Milliseconds
// iOS Only
activityType: "other",
allowsBackgroundLocationUpdates: false,
headingFilter: 1, // Degrees
headingOrientation: "portrait",
pausesLocationUpdatesAutomatically: false,
showsBackgroundLocationIndicator: false,
})
.then(x => {
console.log({ x })
resolve(true)
})
.catch(err => {
console.log({ err })
resolve(false)
})
})
}
In my case I've used react-native-location.
In recent versions of iOS (>= iOS8?) and Android you can show the app permissions dialog and let users turn on/off permissions such as camera and location. There is a native module to do that: https://github.com/yonahforst/react-native-permissions
Related
So, I've been reacting for quite some time. I am facing a problem handling API and making it interact with small widgets in mobile app.
What I am trying to do ?
I am on a Screen(A) and on click of a button I go to Screen(B). Simple. Now in Screen(B) I fill up a form and submit it. While the form is submitting I have to show a Loading component. Finally the API responds if the request was "Success" or a "Failure".
If it was Success - Navigate back to Screen(A) and show a toast message(Boom..boom..) on screen(A).
If it Failed - Be in Screen(B) and show a toast message(yes, with a failure message).
My Approach
Let's start with reducers. I have following reducer state -
{
forSubmitRequest: false, // false - API has been trigerred to submit form
formSubmitRequestOver: true, // true - request is over
formSubmitRequestStatus: true // true - success
}
Now my actions are as follows -
case FORM_SUBMIT_REQUEST:
return {
...state,
formSubmitRequest: true,
formSubmitRequestOver: false,
formSubmitRequestStatus: false,
};
case FORM_SUBMIT_REQUEST_SUCCESS:
return {
...state,
formSubmitRequestOver: true,
formSubmitRequestStatus: true
};
case FORM_SUBMIT_REQUEST_FAILED:
return {
...state,
formSubmitRequestOver: true,
formSubmitRequestStatus: false,
};
case FORM_SUBMIT_REQUEST_DOWN:
return {
...state,
formSubmitRequest: false,
formSubmitRequestOver: true
};
Here's my coding logic in Screen(B)
const [formSubmitReq, setFormSubmitReq] = useState(false);
const [showErrorFormSubmitToast, setShowErrorFormSubmitToast] = useState(false);
useEffect(() => {
if (showErrorFormSubmitToast) {
Toast.show({
type: 'error',
text1: 'Error',
text2: 'Could not submit.',
topOffset: ResponsiveSize(0),
onHide: () => {
setShowErrorFormSubmitToast(false);
},
});
}
}, [showErrorFormSubmitToast]);
if (
formSubmitReq &&
props.teacher.formSubmitRequest &&
!props.teacher.formSubmitRequestOver
) {
return <Loading msg="Submitting form..." />;
}
if (
formSubmitReq &&
props.teacher.formSubmitRequest &&
props.teacher.formSubmitRequestOver
) {
if (props.teacher.formSubmitRequestStatus) {
props.navigation.goBack();
return <></>;
} else {
setFormSubmitReq(false);
setShowErrorFormSubmitToast(true);
props.handleCreateFormSubmitDown();
}
}
Logic in Screen(A)
const [showSuccessFormSubmitToast, setShowSuccessFormSubmitToast] =
useState(false);
useEffect(() => {
if (showSuccessFormSubmitToast) {
Toast.show({
type: 'success',
text1: 'Success',
text2: 'Successfully submitted.',
onHide: () => {
setShowSuccessFormSubmitToast(false);
},
});
}
}, [showSuccessFormSubmitToast]);
if (
!showSuccessFormSubmitToast &&
props.teacher.formSubmitRequest &&
props.teacher.formSubmitRequestOver &&
props.teacher.formSubmitRequestStatus
) {
console.log('Prep show toast');
setShowSuccessFormSubmitToast(true);
props.handleCreateFormSubmitDown();
}
Lastly this function - handleCreateFormSubmitDown just fires action - FORM_SUBMIT_REQUEST_DOWN
Gist of the code ->
I am trying to show Toast on Screen(A) only when request was success and request was actually fired. Not it may happen that request was success and I came back to Screen(A) now I simply navigate to Screen(B) so I have to make sure toast isn't visible as request wasn't fired. Now when reducer state updates then Screen(A) also gets rendered causing some nast things.
Can anyone point out what strategies they follow in such scenarios and where I could improve here?
One way to do this is to utilize route params from react-navigation
In screen B
...
useEffect(() => {
// Navigate to screen A with a route param
navigation.navigate(A, { displaySuccessToast: true });
}, [showSuccessFormSubmitToast])
In Screen A
...
useEffect(() => {
if (route.params.displaySuccessToast) {
Toast.show({
type: 'success',
text1: 'Success',
text2: 'Successfully submitted.',
onHide: () => {
// Hide Toast Action
},
});
}
}, [route.params]);
Other wise you can use a global state management solution like redux
Its the same logic. You need to update a flag in the global state. Then navigate to the earlier screen and check if this flag is set and render the toast message.
I would like to know how I can do to detect if during any moment of the execution of an Ionic 4 app (not only at the beginning of the app), the user manually turns off or on the GPS.
What Ionic 4 event can I hear from the GPS, to be able to alert at any time of the execution of the app if the user turned off or on the GPS?
Thank you very much and sorry for my bad English
My code on app.component.ts:
initializeApp() {
this.platform.ready().then(() => {
this.screenOrientation.lock(this.screenOrientation.ORIENTATIONS.PORTRAIT_PRIMARY);
this.platform.backButton.subscribeWithPriority(9999, () => {
document.addEventListener('backbutton', function (event) {
event.preventDefault();
event.stopPropagation();
}, false);
this.presentAlertConfirm();
});
this.geolocation.getCurrentPosition().then((resp) => {
this.global.origin = { lat: resp.coords.latitude, lng: resp.coords.longitude };
this.global.latitude = resp.coords.latitude;
this.global.longitude = resp.coords.longitude;
}).catch((error) => {
console.log('Error getting location', error);
});
let watch = this.geolocation.watchPosition();
watch.subscribe((data) => {
this.global.origin = { lat: data.coords.latitude, lng: data.coords.longitude };
this.global.latitude = data.coords.latitude;
this.global.longitude = data.coords.longitude;
}, error => {
console.log('Error getting location in WATCH', error); //error handling //
})
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
You have to watch for some GPS change. As proposed in the Ionic 4 documentation and as you have done correctly:
this.watch = this.geolocation.watchPosition();
this.watch.subscribe((data) => {
// usable data
}, (error) => {
// some error
}, { timeout: 30000 });
timeout: The maximum length of time (milliseconds) that is allowed to pass without receiving a set of coordinates.
And remeber to unsubscribe on destroy:
public ngOnDestroy(): void {
this.watch.unsubscribe();
}
In my application, I am using react-native-geolocation-service to get location updates.
For IOS application it works just fine, but for the android application It gives me this error
I have enabled permission and all, here is the watcher
this.watchId = Geolocation.watchPosition(
position => {
// this.setState({ location: position });
// console.log(position);
console.log('got position ')
console.log(position)
this.dispatch({
type: UPDATE_LOCATION,
location: position
})
},
error => {
// this.setState({ location: error });
console.log('error occured')
console.log(JSON.stringify(error))
console.log(error);
},
{
enableHighAccuracy: true,
distanceFilter: 0,
interval: 10000,
fastestInterval: 5000
}
);
No matter if i run the app in an emulator, or a android device error stays the same.
Code is just working fine for ios
In options parameter create ternary. Check mobile Platform, if Android - empty brackets, else - receive object like you need. Something like this:
Platform.OS === 'android' ? {} : { enableHighAccuracy: true, timeout: 20000, maximumAge: 10000 }
I'm using Express Graphql server with react native and Relay. My device does connects to the subscription but it does not subscribe to it. Here's my index.js on the server
const subscriptionServer = SubscriptionServer.create(
{
execute,
subscribe,
schema,
onOperation: (message, params, webSocket) => {
console.log(params)
return params;
},
onConnect: () => {
// My device does connects
console.log("client connected")
}
},
{
server,
path: '/subscriptions'
},
);
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `ws://127.0.0.1:8080/subscriptions`
}));
server.listen(PORT, ()=> {
console.log("Groceries running on port " + PORT)
console.log(
`subscriptions is now running on ws://localhost:${PORT}/subscriptions'}`
);
});
The resolver for subscription on the server, it was quite troublesome to figure out since everyone is using executable schema from apolloGraphql.
export default {
type: OrderEdges,
args: {
ShopId: {type: GraphQLID},
},
subscribe: withFilter(() => pubsub.asyncIterator('orderConfirmed'), (payload, variables) => {
console.log(payload)
console.log(variables)
return payload.orderConfirmed.node.ShopId == variables.ShopId;
}),
}
Now the react-native client. My subscription setup with relay environment.
const setupSubscriptions = (config, variables, cacheConfig, observer) => {
const query = config.text; //does console logs the query
const subscriptionClient = new SubscriptionClient(`ws://192.168.0.100:8080/subscriptions`, {reconnect:true});
subscriptionClient.request({query, variables}, (err, result) => {
console.log(err) // doesn't get call inside the request method
observer.onNext(data:result)
})
}
My subscription method,
export default function() {
const variables = {
ShopId: shop.getShop()[0].id
}
requestSubscription(
environment,
{
subscription,
variables,
onCompleted: (res, err) => {
console.log(res)
console.log(err)
},
updater: (store) => {...},
onError: error => console.error(error),
onNext: (response) => {console.log(response)}
});
}
the component where I'm calling to subscribe,
import subscription from '../../GraphQLQueries/subscriptions/orderConfirmed';
class OrdersBox extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
//initializing subscription
orderSubscriptions();
}
When the device starts the app, my device is connected to the web socket as I can see the console.log statement inside the onConnect method in SubscriptionServer. But when the payload is published after a mutation, the subscribe method doesn't get called. I can't seem to figure out what I'm doing wrong. Maybe it's some react-native specific config that I'm missing cuz everything seems to work fine when I test it on graphiql.
I can't find any example of react-native and relay subscriptions used with express graphql.
note: Everything is working when I use subscription with graphiql. But not with react-native and relay.
Thanks in advance guys
....
I wasn't returning the subscriptionClient.request method. Adding a return statement solved the problem. You don't have to return when using subscribe method in subscriptions-transport-ws#0.8.3. But version 0.9.1 replaces the subscribe function with request which does require it to return.
try:
function setupSubscription(config, variables, cacheConfig, observer) {
const query = config.text;
const subscriptionClient = new SubscriptionClient(websocketURL, {
reconnect: true
});
const client = subscriptionClient.request({ query, variables }).subscribe({
next: result => {
observer.onNext({ data: result.data });
},
complete: () => {
observer.onCompleted();
},
error: error => {
observer.onError(error);
}
});
return {
dispose: client.unsubscribe
};
}
subscriptions-transport-ws#0.9.1
I'm trying to use react native Geolocation to getCurrentPosition and then as soon as the position is returned, use react native geocoder to use that position to get the location. I'm using redux-observable epics to get all of this done.
Here are my two epics:
location.epic.js
import { updateRegion } from '../map/map.action'
import Geocoder from 'react-native-geocoder'
export const getCurrentLocationEpic = action$ =>
action$.ofType(GET_CURRENT_LOCATION)
.mergeMap(() =>
Observable.fromPromise(Geocoder.geocodePosition(makeSelectLocation()))
.flatMap((response) => Observable.of(
getCurrentLocationFulfilled(response)
))
.catch(error => Observable.of(getCurrentLocationRejected(error)))
)
export const getCurrentPositionEpic = action$ =>
action$.ofType(GET_CURRENT_POSITION)
.mergeMap(() =>
navigator.geolocation.getCurrentPosition(
(position) => Observable.of(
updateRegion(position),
getCurrentLocation(position)
),
error => Observable.of(getCurrentPositionRejected(error)),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
).do(x => console.log(x))
).do(x => console.log(x))
As soon as the app starts, this code executes:
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => { })
store.dispatch(fetchCategories())
store.dispatch(getCurrentPosition())
}
fetchCategories() is an action that has an epic too, but that is working. dispatching the getCurrentPosition() action runs through the epic above. The only output that I can see is that my reducer handles getLocationRejected() as it console logs this:
there was an issue getting your current location: Error: invalid position: {lat, lng} required
at Object.geocodePosition (geocoder.js:15)
at MergeMapSubscriber.project (location.epic.js:17)
at MergeMapSubscriber._tryNext (mergeMap.js:120)
at MergeMapSubscriber._next (mergeMap.js:110)
at MergeMapSubscriber.Subscriber.next (Subscriber.js:89)
at FilterSubscriber._next (filter.js:88)
at FilterSubscriber.Subscriber.next (Subscriber.js:89)
at Subject.next (Subject.js:55)
at Object.dispatch (createEpicMiddleware.js:72)
at Object.dispatch (devTools.js:313)
Here is my reducer:
const searchPage = (
initialLocationState = initialState.get('searchForm').get('location'),
action: Object): string => {
switch (action.type) {
case GET_CURRENT_LOCATION_FULFILLED: {
return action.payload
}
case GET_CURRENT_LOCATION_REJECTED: {
console.log('there was an issue getting your current location: ',
action.payload)
return initialLocationState
}
case GET_CURRENT_POSITION_REJECTED: {
console.log('there was an issue getting your current position: ',
action.payload)
return initialLocationState
}
default:
return initialLocationState
}
}
Is there anything obvious I am doing wrong? My attempt to debug by adding .do(x => console.log(x)) does nothing, nothing is logged to the console. updateRegion() never does fire off because that dispatches an action and the reducer UPDATE_REGION never executes. But the execution must make it into the success case of getCurrentPosition() eg:
(position) => Observable.of(
updateRegion(position),
getCurrentLocation(position)
),
must execute because the getCurrentLocation(position) does get dispatched.
Where am I going wrong?
What would be your technique for using an epic on a function which takes a callback function? getCurrentPosition() takes a callback and the callback handles the payload. Basically if you remove Observable.of( from inside getCurrentPosition(), that's how getCurrentPosition() is correctly used - and has been working for me without redux-observable.
Wrapping anything in a custom Observable is fairly simple, very similar to creating a Promise except Observables are lazy--this is important to understand! RxJS Docs
In the case of geolocation, there are two main APIs, getCurrentPosition and watchPosition. They have identical semantics except that watchPosition will call your success callback every time the location changes, not just a single time. Let's use that one since it's natural to model it as a stream/Observable and most flexible.
function geolocationObservable(options) {
return new Observable(observer => {
// This function is called when someone subscribes.
const id = navigator.geolocation.watchPosition(
(position) => {
observer.next(position);
},
error => {
observer.error(error);
},
options
);
// Our teardown function. Will be called if they unsubscribe
return () => {
navigator.geolocation.clearWatch(id);
};
});
}
geolocationObservable({ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 })
.subscribe(
position => console.log(position),
e => console.error(e)
);
// will log every time your location changes, until you unsubscribe
Since it's now an Observable, if you only want the current location you can just do .take(1).
So using it inside your epic might be like this
// If you want, you could also use .share() to share a single
// underlying `watchPosition` subscription aka multicast, but
// that's outside the scope of the question so I don't include it
const currentPosition$ = geolocationObservable({
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 1000
});
export const getCurrentPositionEpic = action$ =>
action$.ofType(GET_CURRENT_POSITION)
.mergeMap(() =>
currentPosition$
.take(1) // <----------------------------- only the current position
.mergeMap(position => Observable.of(
updateRegion(position),
getCurrentLocation(position)
))
.catch(error => Observable.of(
getCurrentPositionRejected(error)
))
);
As a side note, you might not need to dispatch both updateRegion() and getCurrentLocation(). Could your reducers just listen for a single action instead, since they both seem to be signalling the same intent?