-
Notifications
You must be signed in to change notification settings - Fork 91
Workflow example
Chain of events that occur on app loading without user interaction.
On app start, the UI for the MainFragment
is created inflating the correspondent XML files and setting the listeners for the buttons, gestures and map. Before adding the listeners to the map, the map should be retrieved, this can fail because [Play Services are not correctly configured in the device] (http://developer.android.com/google/play-services/setup.html#ensure).
If this fails all UI interface is disabled with setEnabled
method for all button views and disabling the options menu and an intent is send to Goolge Play services that will a display a dialog to update the services. Also in the blank a map a button will appear to make the update. When the app returns to foreground (presumably coming for a successful installation of Play Services) the map is retrieved again and if this is done OK all gets re-enabled. This way the app never is in a failure state.
Selected server on last app usage is retrieved from the database and the bounds are drawn in the map. Map is centered to the center of the selected server.
Previously selected tiles are restored to the map and max zoom level is set to avoid problems zooming too much with custom tiles.
At this point the previous state of the app if is not a fresh start is restored. This is performed with [saved Instance bundle] (http://developer.android.com/training/basics/activity-lifecycle/recreating.html#RestoreState) received in onActivityCreated, if this bundle is null it means that is a fresh start and a field that allows the rest of the app to be aware of this is set.
Note that this part will be repeated each time the app comes from the background, because the method is triggered in onStart method, part of the [Android lifecycle] (http://developer.android.com/guide/components/fragments.html#Lifecycle).
-
A connection to LocationClient is requested in OnStart method of the fragment, setting this fragment to receive the callbacks derived from the connection.
-
OnConnectionFailed method will be activated if the connection fails, an intent to Play Services will be sent and that activity will take care of the problem.
-
OnConnected method will be triggered on a normal execution. This method can behave of several ways depending on what is the state of the app:
-
No server is selected:
ServerSelector
task will be always executed and will try to autodetect a server if the preference is checked and a valid user's location is obtained. -
Server is selected and autodetect is enabled:
- User not in server bounds and clearly changed his position or it's the first time that the server is trying to be detected, hence last position checked for servers is null: the app will try to autodetect a server.
- Otherwise:
ServerSelector
task won't be triggered because will be a pointless disruption for the user.
-
Server is selected and autodetect is disabled. In this case this will be only executed at first start because if the server is not changing there is no point in changing the camera from the position set by the user. First start is detect checking if [saved Instance bundle] (http://developer.android.com/training/basics/activity-lifecycle/recreating.html#RestoreState) is null.
- User in server bounds: camera will be moved to user's location so he will see his location in the map (it's marked as a blue dot/arrow and [provided by Maps APi] (http://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setMyLocationEnabled(boolean)).
- User not in server bounds: camera will be centered in server center and a marker will be set to this center to be set as default start location. This way the user can directly move the marker and will not have as default start a location an useless, for him, "MyLocation".
-
The main parameters to be set are start/end location, date & time to travel and travel mode & type.
There are three different ways to do this:
- User starts typing.
- A listener for the text box detects that the text has been changed and sets a field to inform of this for the correspondent text box. There is another field to don't trigger this process if the text box was changed by the app.
- The user finish typing hence he clicks in the other text box (or changes the focus in an other way) or presses done button. `Task
- Geocoding is triggered for the text contents.
- Geocoding result are obtained,
GeocodingTask
has finished his exection and passes the control to the callback class that were passed,MainFragment
:- More than one result: before the task passes the control launches a dialog showing all the locations to select the preferred location. Once is selected last location is passed to the
MainFragment
. - No results: an empty list will be passed to the callback.
- One result : obtained address is passed to the
MainFragment
through the callback.
- More than one result: before the task passes the control launches a dialog showing all the locations to select the preferred location. Once is selected last location is passed to the
- Callback for geocoding is executed. If there were no result an error message is shown, otherwise:
- Sets the start/end location field.
- Sets the text box to display the result to the user.
- Adds, moves the marker to the new position.
- Camera is centered to the new location or to an area to fit start and end locations if is not the first location entered.
- User taps or long taps in the map:
- Tap: a marker will be set for the text box that has the focus.
- Long tap: we can choose between start/end marker
- The app receives the marker position.
- Marker is inside bounds, marker is set:
- Selected marker is moved or added to the selected location.
- Text box location is updated to marker coordinates.
- A toast is displayed to inform the user.
- Preference key to control if "MyLocation" is set as origin or destination is disabled if necessary.
- If preference key of "Intelligent Markers" is set to enable, reverse-geocoding will be triggered to the position of the marker.
- Marker is outsutide bounds: an error toast is displayed.
- Marker is inside bounds, marker is set:
- If geocoding was requested:
- Geocoding result is closer than a defined constant to marker coordinates: result is accepted.
- Marker is moved to the received location.
- Location start/end field is updated.
- Text box is updated to the received address.
- Geocoding result is not close enough: result not accepted:
- Whatever, text box is updated with "Marker close to [geocoding result]", although geocoding location want be used.
- Geocoding result is closer than a defined constant to marker coordinates: result is accepted.
Three options can be selected:
-
Current location: travel from user's current location obtained from Location Client.
- User's location is not available: no changes will be performed
-
Contact address: and intent is sent requesting the system contact's app all contacts with an address set.
- If a contact is selected
MyActivity
, as is the attached activity, will handle the situation in itsonActivityResult
method:- Address will be set as text box contents.
- Geocoding will be triggered to obtain the location for that address (using the task and same procedure previously described).
- If a contact is selected
-
Point on map: text box whose menu was clicked is selected and a helping message is set as text box hint. Then a point will be added following the use case of the previous section.
Date and time are set in a dialog that is triggered by a "clock" button in the main UI. This dialog is implemented by a Fragment class, following latest Android guidelines, called DateTimeDialog
.
The UI consists in two [pickers] (http://developer.android.com/guide/topics/ui/controls/pickers.html) to choose date and time and a [spinner] (http://developer.android.com/guide/topics/ui/controls/spinner.html) to choose whether the desired date will be used to set the arrival or the departure time.
![Date and time detailed] (https://docs.google.com/drawings/d/1mItBgL4DqQw47G2Lq6pHNdjP92bN6BcGtD8DB1qN45g/pub?w=1127&h=826)
-
User clicks on the button.
-
App listener for the button, which is located in
MainFragment
, is called:- Fragment is created removing previous instances if any.
- A bundle is attached to the new fragment to pass the initial hour. This is used to display the dialog next time with the date&time the user has entered, because it is store in
MainFragment
. Firs time will be actual time.
-
Method to show the [Dialog Fragment] (http://developer.android.com/reference/android/app/DialogFragment.html) is called, passed control flow.
-
DateTimeDialog initializes the date and time to the values of the bundle. Received date and time are also used to set the minimum date to choose.
-
Dialog is shown to the user.
-
User uses controls to select the preferred date and time.
-
User press OK.
-
DateTimeDialog OK button listener receives the signal:
- Retrieves the data from the pickers and the spinner.
- Obatins the activity
MyActivity
for intercommunication with the other fragment and calls the public method of the activityonDateComplete
.
-
MyActivity
callsonDateComplete
in the callback class that was defined previouslyMainFragment
. -
MainFragment
sets the fields that hold date, time and arriveBy value of the spinner. This values will be used to initialize next timeDateTimeDialog
and to be included in the request when it's triggered.
This parameters are grouped in a [drawer layout] (http://developer.android.com/reference/android/support/v4/widget/DrawerLayout.html) which is a fairly used solution in Android nowadays as an elegant way of maximize space for the main interface, in this case the map.
This drawer is open if the user makes the gesture of sliding from the edge of the device screen or presses the handle in the bottom left. To allow normal dismissing of the drawer on press back button, a listener is implemented to release focus from text boxes when drawer is opened, hence the focus passes to the clickable drawer.
-
Trip type: a [listview] (http://developer.android.com/guide/topics/ui/layout/listview.html) is used to present this parameter and save the state of the preference. When the request is processed selected value is retrieved from the listview. The values offered by this list are dependent to the travel mode, as there are different optimizations by travel mode, as bike custom optimization. A listener is implemented to detect when the custom optimization for bike is selected to enable/disable bike parameters.
-
Travel mode: also managed by a listview, this time also a listener is necessary to change the optimization type accordingly with the travel mode selected.
-
Bike options: this was a challenging part because there is no component in Android similar to the [bike triangle] (https://github.com/openplans/OpenTripPlanner/wiki/Bike-Triangle) present in the web interface.
-
To manage this there is a full layout on the low part of the drawer composed by a title bar, tags to better understand the bar and a custom seek bar with range support.
-
This part was challenging, because the addition of the values for the three parameters should be always one, and there is also no easy way to relate different seekbars in Android (also we should decide which one to move). The final solution was to use a seekbar intended to choose a range of values to a different function with the help of custom colors for each interval. Thanks to the creators of this [range-seek-bar] (https://code.google.com/p/range-seek-bar/) and to the user who added [colors support] (https://code.google.com/p/range-seek-bar/issues/detail?id=12) which is the version used. The only changes made to this last version is a trivial function to change the bar colors while the bar is initialized. This is the only way to show the bar as faded when is disabled, because it does not have XML support, to add colors to enable/disable state.
-
The seekbar is connected through a callback to the
MainFragment
and when the values are updated they are registered to a field to use them later on in the request.
-
There are two different ways to trigger routing process, pressing "done" button in the sliding keyboard when the end location is entered or pressing the "plan trip" button (actually with a magnifying glass icon). In the first case geocoding will be processed if is needed running OTPGeocoding
task and running the request when the callback for the geocoding result is activated, only if this callback receives a valid result.
![Trip request detailed] (https://docs.google.com/drawings/d/1uLA8o69KI80dPV-VxmEesuVs5eBaT9jj5uqt4lKVYH0/pub?w=1127&h=887)
-
User taps on UI button for plan trip.
-
MainFragment
callback is activated: If an old route is drawn in the map it's removed.- The process stops and user is informed of the failure if:
- Start or end location are not set.
- Both locations are the some (actually it only detects this if both are "MyLocation").
- "MyLocation" is used but user's location can not be retrieved.
- The process stops and user is informed of the failure if:
-
All parameters are gathered. And included in a
Request
object of the OTP backend throughopentripplanner-pojos
helper project. This class organizes introduced parameters so is more easy to retrieve them when the URL will be created and also shows all the possible parameters for a request. Parameters are obtained from:- Fields of
MainFragment
. - Travel mode and trip type that are directly obtained from the respective listviews.
- Max walk distance and accessible routes are retrieved from the preferences, were are stored.
- Fields of
-
A weak reference to the activity is obtained (because of the reasons explained in [task section of App structure] (https://github.com/CUTR-at-USF/OpenTripPlanner-for-Android/wiki/App-structure#asynchronous-tasks)) and
TripRequest
task is created and executed. Keyboard is hidden to have the map full screen as it's no longer needed. -
TripRequest
tasks creates a waiting dialog inonPreExecute
method, before starting real execution to give the user feedback that the request has been started. -
Request
object is used to create the actual URL for the request to the selected server and the task requests this URL. -
A "JSON" object is obtained in response from the server.
-
Received object is mapped with Jackson to the
Response
class ofopentripplanner-pojos
.- If there is an error with this mapping the exception will be captured and the user will be informed.
-
Waiting dialog created in
onPreExecute
method is dismissed. -
If response was obtained correctly it's passed to the task callback and the task finishes it's execution.
-
MainFragment
receives the callback:- [Itineraries] (http://opentripplanner.org/javadoc/0.9.2/org/opentripplanner/api/model/Itinerary.html) and [legs] (http://opentripplanner.org/javadoc/0.9.2/org/opentripplanner/api/model/Leg.html) for them are analyzed and route values are stored.
-
Itineraries are passed to
FragmentListener
to make them available to other activities and fragments that use this listener (as the step-by-step directions). -
Route is displayed in the UI:
- Route is drawn to the map:
- A polyline displaying all the route.
- Mode markers to each mode of transport change informing of:
- Transit segment: time of departure, head-sign of the transport and time of departure.
- Connection walk/bike segment: where to go to pick the next transit medium and time and
- Map is centered and zoomed to just fit all the trip in the screen. distance to get there.
- Itineraries spinner from the bottom of the UI is filled with received itineraries.
- Start/end location are copied to detach them from text boxes and remember origin and destination for the trip.
- Route is drawn to the map: