Devember - Road Trip Tracker (Android Application)

Day 13 Lacking motivation

Just a brief update today, not really feeling inspired to work on the project so I’ve just done a little bit of UI tweaking and added a stub for the TrackingActivity

One of my goals for #devember2k19 was to get a little better at Android UI design and to try to make my layouts a little more modern looking but I feel like I am cutting corners so I can get back to writing code. To try to get some inspiration back I am going to spend a little time watching some mobile UX design videos I found.

Shecks

3 Likes

I started android development at the beginning of this year, I found the hardest part for me was to get motivation to any of the UI work. Your UI polish looks so much better than mine ever dreamed of being. Basically the app is something that pretty comparable from a UI perspective for what your doing. I’ve literally bookmarked this page just so I can come back here and get ideas for the UI later.

So close to what I want in fact that I would literally probably pay you to implement it but mines in java lol.

Super cool dude!

2 Likes

Thanks for the feedback, I really appreciated it.

I tend to get motivation for an app visually and usually mock up UIs to get a feel for how the application will function, the components I will need to write and how the application will be put together.

But, if I don’t find the UI appealing my motivation to work on it goes until I come up with something I like (Usually by stealing ideas from other apps :innocent:)

I’ll keep tweaking it though, sooner or later I should come up with something I like (I’ve just redone the route history list this morning)

Shecks

2 Likes

Day 14 Painting by numbers

An early update today.

In my seemingly never ending quest to come up with a UI that I am happy with I have been pixel pushing again today. This time I decided to re-work the item layout for the route summary lists.

When I originally did the layout I threw it together using nested LinearLayout views.
Now Mr. Google recommends using the new buzzword compliant ConstraintLayout these days for almost everything, but it’s a pretty heavy layout to inflate so not ideal when used in lists (e.g RecyclerView especially if the list can contain a lot of items)

I wasn’t happy with the LinearLayout XML soup I had created and while looking at the UI I realised that it’s pretty much a table style layout … and then remembered … TableLayout is a thing.

Some XML fun later and we have this:-

And while I was under the bonnet (or hood for the Americans) … I decided to get rid of the Fisher Price™️ style “My First Layout” giant profile icons:-

I introduced a little flash of colour to the layout by assigning a colour tag to each profile type (I will allow the user to customise the colour when setting the distance and speed units for each profile)

I haven’t decided on the default colours for the profile tags yet but I kind of like them so far and I am suitably motivated now to do a little more work on the app (I can add an option to hide them if they don’t appeal to others)

Yesterday I made a start on the layout for the TrackingActivity so I plan to get back to that either today or for tomorrows task.

Shecks

4 Likes

Day 15 Fun with flags tables

Today I continued work on the layout for the TrackingActivity. This will be the activity that shows live stats as route tracking is in progress.

My plan is to have some live stats at in the top panel and to plot the route on the map below. For the moment I will get it working with some generic stats for all profiles, but I think it would be nice if the stats panel was based on the current profile for the route (e.g Time might be the most important stat for Walking or Running but Speed might be more important for Cycling or Driving etc)

The generic panel looks a little cramped so I will probably change it to have the primary stat for the profile as the largest element and the secondary stats in a smaller font to the right of the panel.

And continuing on from the layout tweaks I did for the route summary lists, I decided to carry that layout style over to the RouteDetailFragment so it now looks like this:-

Note I am still deciding whether I want to use uppercase for the labels or not so there is an inconsistency between this screen and the rest.

It looks like there’s finally some sort of theme emerging:-

Apart from having to revisit the HomeFragment layout I reckon I have about 85% of the UI design work (the horrible part) done, hopefully I can get stuck into making it functional soon. This should actually be fairly quick since I am re-writing an application I have already prototyped which is about 90% functional so I don’t need to figure out how its going to work.

Shecks

4 Likes

Day 16 Escape from XML hell

Today I spent some time working on the location tracking components for the application. This is implemented as a foreground local service (in process service) that will only be active while the user is in the TrackingActivity.

The service is unbound and stopped if the user exits the TrackingActivity but will remain running as a foreground service if the application is pushed to the background.

As of Android 8.0 (API >= 26) there are a lot of limitations / restrictions applied to services both for security and performance reasons. There’s also a distinction made between background and foreground services with background services being more heavily restricted.

Road Trip Tracker only needs to use the service so that GPS location fixes are still received while if the application UI is pushed to the background so I can use a foreground service for this purpose.

One of the requirements for using foreground services is that the application must display a persistent notification to the user while the service is active so even if the application UI is not visible the user is aware that a service belonging to the application is still running.

This is done using the Notification APIs to add an icon to the status bar and notification information to the notification drawer and the setForeground() API to tell the OS that we’re a foreground service. (as far as I know, if these calls are not made on devices running Android 8 or later the application will be killed)

For the moment I just add an icon and simple message notification, but it would be nice to have the tracking duration here and perhaps the controls to pause or stop tracking.

In my original “prototype” application I communicated between the service and the TrackingActivity using intents and a BroadcastReceiver via the LocalBroadcastManager component, but it seems that LocalBroadcastManager has since been deprecated and branded evil by Mr. Google with LiveData being the new favourite for sharing observable data between application components (including local services)

So the next order of business will be to figure out a nice way to implement that cleanly.

Shecks

4 Likes

I’m learning stuff…cool. I didn’t think that there would be anything at a level that would make sense to me. The info on the tracking…

2 Likes

Day 17 Lazy Day

Not much done today.

I just continued working on the communication between TrackingActivity and TrackingService. As Google have deprecated LocalBroadcastManager I have started to implement the communication using observable LiveData instead.

To see how communication between the UI and service will work using LiveData I decided to start with the service state. So the service now exposes its state enum as LiveData<TrackingService.State>. This data is then observed by the UI so it can react to state changes.

It seems to be working as expected although I will have to do additional work to handle device rotations due to the differences in how the LiveData method works and how the original prototype worked with it BroadcastReceiver

I will continue with that tomorrow, then move on to actually collecting location data.

Shecks

3 Likes

Day 18 Button button, whose got the button?

Today I continued working on the state code for the TrackingService and having the UI buttons react and update correctly to the state changes.

I ran into a little issue while I was testing the state changes. It seems I was lazy when I wrote the initial prototype application and locked the TrackingActivity in portrait mode so that I wouldn’t have to deal with screen rotations.

Because I want to do things correctly this time (and the tracking screen in an app like this should really work in landscape anyway), I left it unlocked and this exposed an issue in how I am using the local service.

On Android an applications UI is considered disposable as it can be destroyed at anytime and this includes device orientation changes (or configuration changes as Google calls them). An application has to be prepared for this and should save/restore its state as required but because I originally had the screen locked in portrait mode I was just unbinding from my service when the TrackingActivity was destroy. This caused my service to be stopped because by default a local service will be stopped once the last client unbinds from it.

To solve this I changed how I start and interact with the service. Now, instead of just binding to the service (which starts it if it has not already been started) I explicitly start the service then bind to it. When a service is explicitly started in this way it will stay running until it is explicitly stopped. The result is the service does not get killed when my activity unbinds from it during screen rotation. When the activity is re-created after the rotation, I just rebind to the running service and get its state.

Then it was a simple case of restoring the UI each time the device is rotated … or not …

This took a lot longer than I had planned because I wanted to be “fancy” and have my buttons show/hide, change colour etc based on the service state …

Service State Buttons
Stopped Single green “Start” button shown
Started Start button changes to a red “Stop” button, green “Pause” button and lock buttons are shown
Paused Red “Stop” button remains, caption of green “Pause” button changed to “Resume” and lock button remains visible

Anyway, after a lot of faffing around and testing each state through device rotations I finally had it working

I’ve also implemented the lock button which disables the two control buttons to prevent the user from accidentally stopping tracking. The device back button will also prompt the user to save the route if pressed.

So, that was a lot more work than expected (a lot of the time was actually spent writing XML colour selectors to get the new “pain in the ar$e” MaterialButtons to show the correct shaded colours when disabled)

Tomorrow I will actually start capturing GPS fixes.

Shecks

4 Likes

Day 19 Big Brother is tracking you!

Today I did a few UI tweaks then made a start adding the actual tracking code to my TrackingService

The service now sets up the Google Location Services and registers a listener to receive location fixes. For the moment I am just writing the locations to the debug log to verify that I am receiving them.

I need to write some additional code to filter duplicates and ignore location fixes if the device hasn’t moved etc so I am going to wait until that is done before writing them to the database.

I did add the code to save the basic route detail header (TRouteDetail) to the database so now I have real data in the History list (albeit with only the route timing stats complete)

If you look closely at the first screenshot below you will see the Android location icon in the status bar on the right. The OS adds this when any application is actively listening for location fixes, in this case it’s me (you’ll just have to trust me on that one :smiley: )

You might also notice that the location tracking icon is not present in the second screenshot, this is because I currently stop listening for fixes when tracking is paused just because it seems right (and would probably save some battery life if tracking is paused for a long time). But going forward I might want to add support for auto resume, in which case I will need to keep listening for location fixes while paused to detect when the device starts to move again.

The last two screenshot show new route entries in the History list and the detail screen (Only the time stats are real, but I needed to test the paused and active timing code anyway)

Now that I can add new route data I started thinking about finishing the item selection code I wrote for the History and Favourites fragments so I can delete routes.

Which is when I discovered this … :open_mouth:

The UI has changed a lot since I wrote that code and it looks like the default item selection check marks are now colliding with route state date + time … so I will have to address that. Also, since I’ve been fiddling with the application style/theme the ActionMode bar has gone all pear shaped … it should be blue with white text and icons … more UI fun to be had there …

But, it’s slowly getting there, next I need to get a stub together for the application preferences settings so that I can access the current profile (all new routes are currently hard-coded as “Cycling” routes), then I need to work on the calculations for the distance, speed etc stats and write the GPS fixes to the database so I can then plot the recorded route on the map.

Shecks

4 Likes

Day 20 Just walking around the yard :cowboy_hat_face:

Today I spend some time getting the GPS location fixes written to the database and doing some of the calculations for the stats.

The application now tracks and displays real data for …

  1. Total route duration
  2. Active route duration (also displays paused duration)
  3. Total distance (In the correct distance metric for the selected profile *)
  4. Maximum speed (also in the correct speed metric for the selected profile **)

* All distances are recorded in meters and converted for display based on the profile settings
** All speed are recorded in meters per second and converted for display based on the profile settings.

Average speed should also be calculated but it looks like there’s a “feature” and it’s not being displayed correctly.

Also, I have decided to calculate both the average active speed (distance / active duration) and the average overall speed (distance / total duration). Although I am not sure if this would be useful, should I just show the average speed while active? Would people be interested in seeing a lower average speed if they had paused tracking?

I’ve also added a singleton Preferences class to manage the application settings. At the moment I only have a single setting to save the selected tracking profile Id (the actual profiles and their settings are stored in the database) and still need to create the UI to allow the user to change it.

Also, the altitude returned from the Location Services isn’t very accurate and I’d like to be able to track max climb (and possibly descent) so I have been looking into using the Pressure Sensor, if available, to record the altitude based on barometric pressure above sea level.

From what I understand, in order for actual barometric altitude to be accurate I need to know the pressure at sea level locally (there are REST APIs I could query for this, but it’s out of scope) but for the moment I only need to measure altitude changes so I think I can get the data I want by measuring the changes from a known base value (the Android SDK defines PRESSURE_STANDARD_ATMOSPHERE for this)

I’m also playing with the idea of recording temperature at each GPS fix too (again, if the sensor is available)

Anyway, I am off to run around my yard again to collect some more data (perhaps I’ll get out for a cycle tomorrow to collect some good data so I can work on plotting the route on a map)

Shecks

4 Likes

Day 21 Getting some exercise

Today I spent my time working on the UI and code to allow the user to select the active profile to use for tracking new routes. I also did a little bit more UI tweaking on the HomeFragment for both portrait and landscape mode.

I also added the code to capture the atmospheric pressure using the devices pressure sensor which I then use to calculate the barometric altitude. While this won’t be accurate enough to display absolute altitude values (because I am using a fixed value for the pressure at sea level) it will be more reliable than the GPS altitude and suitable for calculating climbs based on changes in altitude (I am still writing both the GPS altitude and barometric altitude to the database with each location fix)

Other than that, I fixed a few bugs (average speed calculation, crash on list item selection) and went out for a short cycle to capture some GPS fixes so I can start working on plotting the route on the maps.

Shecks

4 Likes

Day 22 Change of plan

Not much to report today. Just been doing some research on using Google Maps API, there’s a MapView and a MapFragment and I need to decide which to use and where. In the past (a currently in the app) I have just used MapFragment but it’s not ideal to use inside other fragments so more research required.

I’m also looking into alternates to Google Maps, I’m curious about OpenStreetMap MapBox as I haven’t used it before and since #devember is about learning something new, why not?

Other than that I’ve just been doing some code clean up today. I decided that I no longer want to store the colours for the different profiles in the database. I initially wanted to allow the user to customise the colours so since I already store the configuration settings (distance units, speed units etc) for each profile type in the database I figured I might as well store the colour for the UI tags too.

This turned out to be a but of a pain, since there are a few places in the application where I need the colour but I don’t need the other configuration settings and having to query the database asynchronously (via a ViewModel) was just overkill and messy. So I have removed that code and instead associated the default colours with my ProfileType enum. (This enum already contains the string id for the profile name and drawable id for the icon anyway)

In the future I can allow the user to customise the colour by storing an override for the default colour in the applications SharedPreferences. Then I’ll just abstract the code to read from SharedPreferences or fallback on the default color for the profile if no user colour has been set.

Shecks

EDIT: OpenStreetMap doesn’t seem very Android friendly (no official Android API as far as I can see and seems to be geared more toward Web use). MapBox on the other hand looks a lot nicer :+1:

4 Likes

Still Day 22 De-Googling

Just for fun I decided to see what how much work it would take to switch from using Google Maps to MapBox.

Turns out it’s easy enough from a code point of view but there are few issues:-

  1. Adding the MapBox dependency to Road Trip Tracker pushed the method count way over the limit for a single Dex application (which is 65536). I am using a pretty standard set of dependencies and even using the fragmented AndroidX libraries should only be pulling in just what I need. But even after dropping the Google Maps dependency the application was still reference about 80K methods with the MapBox dependency. So because I am targeting API 16+ I had to manually enable MultiDex support and pull in the MultiDex library dependency to get the application to compile again.

  2. MapBox uses LocalBroadcastManager, the very API I avoided when implementing the communication between my UI and Local Service because it has been deprecated in the Android Architecture Components. So, ironically, I was forced to pull in a dependency to androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 (pre-deprecation) to get the application to build.

  3. So far it’s not very stable. I can display maps easily enough but interacting with the maps seems to cause MapBox to crash the application. I can repro this by tapping on the map, perhaps I need to implement some sort of handler, but it shouldn’t crash the application.

The maps are nice, I like not having the dependency on Google Maps and it looks like there’s a lot more functionality in the MapBox library but I am not sure yet if I will check in this code. (At least until I figure out what is causing the crash)

The jury is still out on MapBox …

Shecks

3 Likes

Day 23 Re-Googling

MapBox is back in its box and Google Maps are back in the app :neutral_face:

I was really hoping to be able to get rid of the dependency on Google Maps and was looking forward to using all the additional features provided by the MapBox library but I just couldn’t get the application stable once I switch to MapBox.

The first couple of crashes were just down to me not understanding the requirements for the MapBox widget. The MapView needs to be hooked into the life-cycle of the Activity or Fragment it’s hosted in so it can do house keeping during device rotations and view inflation. This required forwarding calls to onCreate(), onStart(), onPause(), onResume(), onDestroy() and onSaveInstanceState() to the MapBox map widget.

This solved most of the crashes but the application would randomly close, with no errors/exceptions logged to the debug log. On top of this using MapBox forced me to include a library that Google have deprecated and added 8MB to the size of the application APK (the debug build was 12MB with MapBox and is 4MB with Google Maps).

As noted in my previous post, this also meant that the app now exceeded the upper limit for symbol references in a single DEX file so I had to use the MultiDex library just to get it to compile.

So, unfortunately, MapBox is out for the moment but I’ll be keeping an eye on the library and hopefully there are some improvements made when/if they do a release to replace the use of the now deprecated LocalBroadcastManager component.

So, I’ve switched back to Google Maps, but took the time to replace the MapFragments I was using with MapView (it turns out that MapBox is very closely modeled on the Google Maps API and the Google MapView also needs to be hooked into the host Activity/Fragment life-cycle in the same way … but doesn’t cause random crashes).

I was away for most of the day today so didn’t get much more than switching back to Google Maps done. I did however write some support code for using the Google MapViews directly and replaced the tabbed UI in my RouteDetailFragment

I decided to put a preview of the map with route (The route will be plotted soon™️) on the top and made the bottom part of the fragment with the stats scroll-able. The plan is to have an option to make the preview map full screen (probably via double tap or an overlaid button)

But after I made this changes I remembered that I would like this Fragment to work in landscape mode too, so I will probably still need a tabbed UI in landscape … but that’s a job for another day.

Oh, and while I was away today, I remembered to enable tracking so got some testing done and collected over 100Km of GPS fixes :+1:

Shecks

4 Likes

Day 24 Plotting

Spent some time today working on the route maps for Road Trip Tracker.

I made some changes to the layout of the home fragment and added the code to display the route map, profile name and start date/time for the latest route in the database.

The route is also plotted on the map but you’ll have to trust me on that. I’ve blurred the image a little because it’s real data and I don’t think it would be a good idea to share where I did my last minute Christmas shopping or where my home is :male_detective: (There’s probably GPS location in EXIF metadata of all the images I posted anyway)

The route map is also displayed on the route detail fragment. I still need to do some more work on the route plotting as it was some quick and dirty code just to get something on the map. I’d like to add some data (Start time, end time, etc) to the start and end icons and set the zoom and camera position so that the complete route is visible automatically when the map is displayed.

I’ll probably work on the full screen map fragment next.

Shecks

5 Likes

Day 25 Happy Christmas :grinning:

Nothing to see here, just posting for consistency and to satisfy my OCD. Although in fairness I did check in some code at 2am this morning which was technically day 25 :cowboy_hat_face:

Anyway, there won’t be much code written today, I’m off for Christmas dinner and some beer (I’ll at least track the route to my Sisters house and call it acceptance/user testing)

Happy Christmas/Holidays everyone.

Shecks

4 Likes

Merry Christmas! Dude, I’m blown away by this project. Keep up the great work

2 Likes

Day 26 I’ve eaten far too much food … :nauseated_face:

Just did a little bit of work on the app today.

  • Added code to the home screen to display a message when there’s no latest route available to display (e.g the database is empty). I think, eventually, I will display the current location on the map in this case.

  • Added a button to the home screen to open the latest route when clicked (top right of the map panel, beside the route start date/time. (I might move this down to overlay the top right hand corner of the map)

  • Created the stub fragment for the full screen route maps (Touch events are already used for scrolling and zooming the map so I might put an overlay button on the map to open in full screen mode)

  • Fixed a few bugs

Shecks

4 Likes

Day 27 Run to the hills \m/(-_-)\m/

Today I finally got around to writing the code to calculate the route climb stats. I’ve implemented both a maximum elevation gain stat (e.g the highest single climb on the route) and the total cumulative elevation gain (sum of all climbs on route).

I’ve added the new fields, max_elevation_gain and total_elevation_gain to the TRouteDetail database table and written the code to do the calculations while tracking is in progress (I could just calculate the values dynamically based on the GPS fixes stored in the database but figured that for long routes there might be a lot of GPS data so it’s better to calculate them with the rest of the running totals rather than each time the route is retrieved from the database)

I’ve updated the UI of the RouteDetailActivity to display the new values in the distance panel but I have yet to get some test data. I didn’t want to go to the trouble of retroactively populating the fields based on the existing GPS data since I will be dropping the database before my next “testing session” to clean up the schema (For some reason SQLite doesn’t have a DELETE COLUMN as part of the ALTER TABLE statement and I have a couple of deprecated fields to get rid of)

I will just have to go out and cycle up and down a few (small) hills tomorrow :cold_face:

I’ve also been playing around with the UI for the home screen, I was thinking about putting some overlay buttons on the map for the full screen and open latest route options (replacing the button I added yesterday) but I am not sure about it yet.

Need to think a little more about this … :thinking:

Shecks

4 Likes