Product Hunt Mobile Theme with Redux in React Native Part II
In a previous post, we added theming to the app. The next step is adding automatic switching based on sunrise/sunset for the current location.
Since automatic switching of the theme is not a mainstream feature, we’ll disable it by default. If the user switches it on, we’re going to request for location permissions
(iOS Only). Once we’ve been granted permissions from the user, we’ll make a network request to Sunset-Sunrise API.
When I’ve started working on this feature, I wanted to change the theme immediately when sunset or sunrise occurs. That wouldn’t be a good UX experience because the user can be in the middle of the reading comments for example, and this sudden change can be frustrating and unexpected.
Instead, the theme only changes when the user switches on automatic night mode or opens the app.
1. UI
We already have the UI for dark/white theme, so the only addition here is going to be one switch.
2. Actions
In the previous post, the action was quite simple and didn’t have any logic. This, unfortunately, is no longer the case.
We’re going to need three separate actions:
- Toggle automatic theming on/off
- Get sunset/sunrise time
- Get dark/light theme depending on the time
Separating actions should make things simpler: we’ll call each action when the app starts or toggled from settings.
The first is very similar to the one we’ve already added in the previous post.
After enabling/disabling the feature, we should either get sunset/sunrise time or clean up already saved time. We will update toggleAutomaticTheme
after implementing getSunsetSunriseTime
, updateTheme
and reset
actions.
Get sunrise sunset time action
This action is quite complex, so let’s chop it to pieces and see what is going on. First, we’re checking if the automatic theme is enabled – if not, the execution just stops there.
// utils.jsexport function isAutomaticEnabled(state: any): boolean {
return state[constants.MODULE].automatic;
}
Day length changes over the year depending on your location, so we need to pull actual data from the API. In order to prevent spamming the API, we’ll check to see if we’ve pulled the data for the current day:
// utils.jsexport function isFetchedToday(state: any): boolean {
const themeState = state[constants.MODULE]; if (!themeState.sunrise) return false; return moment(themeState.sunrise).isSame(moment(), "day");
}
If we’ve already fetched today, just update the theme.
Finally, it is a time to get location and sunset/sunrise time. Before implementing the action, we need to make a couple of changes to be able to retrieve current user location. Since we need location only on foreground for iOS we do need to make any changes, NSLocationWhenInUserUsageDescription
is in Info.plist
already. For Android in AndroidManifest.xml
add the following permission request:
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />
Now we can get the location. If it’s successful, we’re going to call the API.
navigator.geolocation.getCurrentPosition(position => {}, error => {});
In case there is an error — maybe user didn’t allow to get his access, or something else went wrong — we disable automatic theme. But, if we successfully retrieve current position we’re going to call Sunset-Sunrise API .
update theme
Update theme action is simple compared to getSunriseSunset
. Most important thing there is getting a theme for the current time. If it’s during the daytime, we’ll switch to the lighter them. Otherwise, dark theme.
The last one is reset action. It’s not a mandatory one, but I like to keep state clean also dispatching the action will notify the UI about the change.
Finally, we can update toggleAutomaticTheme
if the value is ON dispatch getSunriseSunsetTime
if OFF just reset to the initial state.
3. Reducer
Handling reset and toggle automatic in the reducer are quite straightforward.
Handling sunset/sunrise time is more interesting since the API returns time in GMT. We need to get user’s timezone and get the actual time based on the timezone.
4. Handling app state and theme
What about next time when the user opens the app? We should check what type of theme should be active. Handling the cold start is easy we’re going to dispatch action in componentDidMount().
But what about changing state from background to foreground? We should add a listener for state change and when trigger change if the previous state is inactive or background and next one is active.
Conclusion
I underestimated the complexity of the feature. Initially, I thought it would take an hour or so. 🤦♂
it was a day work and a lot of edge cases. Yes, I do think it is a convenient feature and find it very handy.
Useful links:
Moment Range — https://github.com/rotaready/moment-range
Redux — https://redux.js.org/
Redux-Persist — https://github.com/rt2zz/redux-persist
React Native Device Info — https://github.com/rebeccahughes/react-native-device-info