Forum
Info
Learn
People
MPscurrent
Labs
Grades
MP3current
MP2
MP1
MP0
Setup
Android Studio
Git
Archive
Spring 2019
Fall 2018
Spring 2018
Fall 2017
MP3: Refactoring and More JSON
In MP2, you connected the app to our game server to get and respond to invitations. In Checkpoint 3, youll make it possible to invite people to new multiplayer games and refactor some of your old code to improve it.
When you first started working on the MP, we hadnt covered objectoriented programming yet, so the target mode functionality had to be implemented using only arrays and imperative programming. As you discovered, that was not ideal. Programmers often learn or realize better approaches to problems already solved and revisit old code to improve it. This process is called refactoring. In this checkpoint well refactor some of the code you wrote in MP0 and organize some relevant logic into a class that can more easily be used in the next checkpoint.
Deadlines for Checkpoint 3 vary by your deadline group. MP3 is due at:
8 PM on Sunday, 1132019 for the Blue Group: all labs starting at 2 PM or earlier
8 PM on Monday, 1142019 for the Orange Group: all labs starting at 3 PM or later
Additionally, 10 of your grade is for submitting code that earns at least 40 points by 8 PM on Sunday 10272019 Blue or Monday 10282019 Orange. Late submissions are subject to the MP late submission policy.
1. Learning Objectives
On MP3 you will:
Work with and improve older code
Make web requests that deliver additional information to the web API
We will continue to exercise your class design and Android UI skills including repeatable UI like in previous checkpoints.
2. Assignment Structure
Like in previous checkpoints, we distribute the new MP3 files in a patch to your existing repository.
For information on all classes present after the completion of Checkpoint 3, refer to our official Javadoc.
2.1. Obtaining MP3
To start working on Checkpoint 3, follow these steps:
1.Commit your work just in case you run into any problems applying the patch.
2.Download the MP3 patch file.
3.In Android Studio, do VCSApply Patch.
4.Select the downloaded patch file and press OK to apply it.
5.Change the checkpoint setting in grade.yaml to 3.
6.Do FileSync Project with Gradle Files.
A new run configuration called Test Checkpoint 3 will run the Checkpoint 3 test suite. If you run into a problem obtaining MP3, please get help immediately.
Because the new test suite refers to a class that you need to add, your project will not be able to compile immediately after applying the patch. This will be fixed as you start working on the checkpoint as described in Your Goal.
2.2. Late Submissions
Like in previous checkpoints, setting useProvided in grade.yaml to true causes the app to use our provided components rather than your implementations. For Checkpoint 3, we provide TargetVisitChecker with both MP0style and refactored signatures, AreaDivider, the launch activity, the main activity, the local game setup UI in NewGameActivity, and the gameplay logic in GameActivity.
If you didnt finish the game setup activity in MP1, you can still do MP3. Our provided components will try to fuse the game setup UI you have with our version of the missing parts. However, it would be ideal to get testSetupUI from MP1 done as soon as possible so that you can set useProvided back to false. If youre curious about how the installation of provided components works, you can post on the forum.
As usual, you can always go back and make submissions to previous checkpoints by changing checkpoint in grade.yaml.
3. Tools and Resources
This section provides you with tools and information that you will need to complete the features in Your Goal.
3.1. Lists
Java arrays are useful for storing multiple values of the same type, but an arrays length cannot be changed after its creation. Often its not known in advance how many entries a collection will have, or the collection has items added to or removed from it. In those situations, arrays are inconvenient.
Fortunately, Java provides lists: objects that hold dynamically resizable lists of things. All lists are instances of List, but List is an interface rather than a concrete type. To create a list you create an instance of a List implementation, the most common of which is ArrayList. This is an example of polymorphism.
When you declared arrays, you put the element type before square brackets, like String for an array of strings. List variables are declared like ListString, with the element type in angle brackets. To declare a listofstrings variable and initialize it to an empty ArrayList, you would use code like this:
ListString namesnew ArrayList;
The angle brackets on the right side of the assignment can be empty, which means that the actual list object created holds the same type of values as the variable is declared to contain.
The List type defines many functions that are usable on all lists. The most useful ones are:
add, which adds one element to the end of the list increasing its size by one
size, which returns the current length of the list
get, which gets the element at the specified position in the list
remove, which removes an element from the list decreasing its size by one
For example:
System.out.printlnnames.size;Prints 0the list is empty initially
names.addChuchu;
System.out.printlnnames.size;Prints 1now the list has one element
names.addXyz;
System.out.printlnnames.size;Prints 2now the list has two elements
System.out.printlnnames.get0;Prints Chuchu, the first element in the list
names.removeChuchu;
System.out.printlnnames.get0;Prints Xyz, the first element now that Chuchu was removed
System.out.printlnnames.size;Prints 1the list has one element anymore
Lists can be iterated over with the enhanced for loop:
for String name : names
Do something with name?
Types like List and ArrayList will need to be imported before you can use them. Android Studio can help with this: if you press Tab to autocomplete a type name, any needed import statement will automatically be added at the top of the file.
3.2. Spinners
Dropdown lists are useful when the user has a handful of possible choices. Android provides a Spinner control in the Containers tab of the Palette that produces a dropdown list. It is possible to populate the dropdown dynamically at runtime, but it is much easier to set the entries attribute in the Attributes pane to a string array resource. Then each string in that array resource will be one entry in the dropdown.
Some useful methods of Spinners:
To get the current selection, you can call the getSelectedItemPosition function, which returns the index of the selected item. For example, 0 means that the user selected the first entry.
To programmatically change which item is selected, use the setSelection function, which takes the index of the item to select.
To register a handler that will be run when the user changes the selected item, use setOnItemSelectedListener:
Suppose spinner is a Spinner variable
spinner.setOnItemSelectedListenernew AdapterView.OnItemSelectedListener
Override
public void onItemSelectedfinal AdapterView? parent, final View view,
final int position, final long id
Called when the user selects a different item in the dropdown
The position parameter is the selected index
The other parameters can be ignored
Override
public void onNothingSelectedfinal AdapterView? parent
Called when the selection becomes empty
Not relevant to the MPcan be left blank
;
If youd like more information, you can see Androids guide to spinners.
3.3. Writing JSON with Gson
In Checkpoint 2, you used Gson to read data from parsed JSON. In this checkpoint, youll need to create JSON objects to send to the server.
Gson can help with this too. To create a new JSON object, use new JsonObject. To add a single, simple value like a string or number as a property on an object, call the objects addProperty function, passing the property name and value. For example, this code builds a JsonObject corresponding to the first MP2 JSON example:
JsonObject pointnew JsonObject;
point.addPropertylatitude, 40.109187;
point.addPropertylongitude, 88.227213;
To add a more complicated value like an array or other object as a property of an object, use add instead.
Likewise, to create a JSON array, use new JsonArray. Its add function will add an entry to the end of the array.
This code reconstitutes the more complicated JSON object from the MP2 writeup:
JsonObject cs125new JsonObject;
cs125.addPropertyname, CS 125;
cs125.addPropertyenrollment, 800;
JsonObject locationnew JsonObject;
location.addPropertyname, Foellinger Auditorium;
location.addPropertyallowsfood, false;
location.addPropertylatitude, 40.105952;
location.addPropertylongitude, 88.227204;
cs125.addlocation, location;
JsonArray lectureDaysnew JsonArray;
lectureDays.addMonday;
lectureDays.addWednesday;
lectureDays.addFriday;
cs125.addlecturedays, lectureDays;
Gson objects stringify to the JSON text they represent, so you can pass them to System.out.println to see what JSON youve built. It will be condensed onto one line and difficult to read, so you may find it helpful to paste that into a JSON formatter to see its structure more easily.
3.4. Our API Documentation
To create a multiplayer game, your app will need to make a POST request to the gamescreate endpoint. Since there is a lot of game information rather than just a game ID, the game configuration will need to be uploaded to the server as the body payload of the request. The body should be a JSON object Gson JsonObject instance with these properties:
mode string is the game mode, either target or area
invitees array of objects is the list of players invited to the game, including the user; each object should have these properties:
email string is the invitees email address
team integer is the TeamID code for the roleteam the user is invited to
For target mode only, proximityThreshold integer is the proximity threshold in meters
For target mode only, targets array of objects is the list of targets in the game; each object should have these properties:
latitude double is the latitude of the target
longitude double is the longitude of the target
For area mode only, cellSize integer is the cell size in meters
For area mode only, areaNorth, areaEast, areaSouth, and areaWest all doubles are the latitudelongitude bounds of the area
You may find the example target mode body and example area mode body helpful.
If the game is created successfully, the servers response will be a JSON object with a single game property whose value is the string game ID.
If the game cannot be created, your error handler will be run. The getMessage function on the error object returns a humanreadable string describing the problem.
3.5. Extra Credit API Documentation
If you are attempting the extra credit feature to allow the user to load a predefined set of targets, your app will need to be able to fetch the preset targets lists from the server. Those are accessible by a GET request to the presets endpoint. The servers response will be a JSON object containing this property:
presets array of objects is the list of preset options; each object has these properties:
name string is the humanreadable name of the preset
targets array of objects is the list of targets in the preset; each has at least these properties:
latitude double is the targets latitude
longitude double is the targets longitude
You may find this example response helpful. Do not assume that the note property will always be present on target objects, but feel free to do anything you like with it if its there. You can always ignore it completely.
3.6. Reverting Changes with Git
Version control systems like Git make it possible to retrieve older versions of your code, which is very useful if you accidentally damage a file. Android Studio integrates with Git to allow you to undo revert changes with its UI.
If you would like to put a file back to how it was at the last commit, rightclick it in the Project pane and choose GitRevert. This brings up the Revert Changes dialog, where you can select any additional files you would like to revert. Reverting a file throws away all changes to it since the last commit and is usually not reversible.
For a more surgical approach, Android Studio highlights changed regions of files with colored bars or gray triangles in the left margin of the code editor. Clicking one of these decorations produces a toolbar with a back arrow Rollback Lines button that reverts just the highlighted lines to how they were in the last commit. This rollback method may sometimes be reversible with CtrlZ, but you should still be certain that you want to throw away your changes.
4. Your Goal
When youre done with Checkpoint 3, the game setup activity will allow the user to invite other people and assign their rolesteams. The user will be able to press locations on a map to specify the targets of a target mode game. Creating a game will upload its configuration to the server and make it visible to the invitees, who can then accept or decline the invitation using their app.
While setting up a target mode game, the user might see UI like this:
A video tour of MP3 created by the CA captains 1 is available:
Unless otherwise noted, you can do these sections in any order.
4.1. Target Class
The new test suite, Checkpoint3Test, is initially unable to compile because it refers to a Target class which does not exist, so this must be fixed first. We will be using the Target class primarily in the next checkpoint to help manage a target marker on the map, since the Checkpoint 0 approach of passing coordinates to a changeMarkerColor function is unwieldy 2.
Create the class by rightclicking the package containing all your other Java source files, choosing NewJava Class, entering Target in the Name field, and clicking OK.
Like in previous checkpoints, make sure that the file was created inside our package in the main source set and that it was added to Git.
To see the needed public members of this class, refer to our official Javadoc. You will need to store a Google Maps Marker object in a private instance variable.
To place a marker on a Google map, use the maps addMarker function 3:
Suppose position is a LatLng variable
MarkerOptions optionsnew MarkerOptions.positionposition;
Set any other options you like?
Marker markermap.addMarkeroptions;
To change the color of a marker after it has been created, use its setIcon function 4:
Suppose hue is a hue value like the constants defined on BitmapDescriptorFactory
BitmapDescriptor iconBitmapDescriptorFactory.defaultMarkerhue;
marker.setIconicon;
After completing this task, testTargetClass will pass. You may optionally rework your target mode logic in GameActivity to take advantage of this new class, but otherwise you will not need it again in this checkpoint.
4.2. LatLng Refactor
Functions that take eight parameters, especially all of the same type, can be difficult to use. This is even more unfortunate when some of the parameters really belong together, packaged up into objects. Now that you know how to use objects like the Google Maps SDKs LatLng, weve rewritten LinesCrossDetector.linesCross to accept the lines endpoints as LatLng objects. 5
Download the LineCrossDetector patch and apply it with VCSApply Patch like you do with the checkpoint patches. This will introduce compilation errors in your functions that use linesCross! You need to adjust those to use the improved versions signature, calling it with four LatLng positions.
If Android Studio is unable to apply the patch due to how you fixed the checkstyle errors in Checkpoint 0, or if there are compilation errors inside LineCrossDetector, you can instead copypaste the updated class from this Gist.
Similarly refactor the addLine function in GameActivity to take two LatLng endpoints rather than four double coordinates. You will need to update the functions callers to be compatible with its new signature.
If you make a mistake while refactoring and want to put a file back to how it was at the last commit, see the section on reverting changes.
After completing these tasks, testLatLngRefactor will pass.
4.2.1. Extra Refactoring Practice
You may optionally refactor your TargetVisitChecker methods to take a LatLng in place of the two doubles. Updated Javadoc is available. The Checkpoint 0 tests are forwardcompatible with this change. After doing that, youll probably want to use the getPositions function of DefaultTargets rather than getLatitudes and getLongitudes in your GameActivity target mode setup.
Better yet, you may take advantage of your new list skills to keep track of the target mode game state entirely inside GameActivity. If TargetVisitChecker is removed, the Checkpoint 0 test results will be allornothing based on the result of testTargetModeGameplay.
TargetVisitChecker will be removed entirely in the next checkpoint and GameActivity will be significantly remodeled then, so dont get too attached to either.
4.3. Targets Map
In Checkpoint 1 you made it possible for the user to select the area for area mode by panning and zooming a Google Maps control. Now youll add a similar map control that allows the user to choose the targets for a target mode game by pressing to add a target and clicking a target to remove it.
To add another map to the game setup activity, open activitynewgame.xml in the UI designer, copy the areaSizeMap fragment, paste it inside the target mode settings layout 6, and change the copys ID to targetsMap.
In NewGameActivitys onCreate we provided this chunk of code:
SupportMapFragment areaMapFragmentSupportMapFragment getSupportFragmentManager
.findFragmentByIdR.id.areaSizeMap;
areaMapFragment.getMapAsyncnewMap
areaMapnewMap;
centerMapareaMap;
;
This gets a reference to the areaSizeMap fragment and registers a handler that will be run when Google Maps creates the map. When the map is available in getMapAsync, it is stored in the instance variable areaMap and passed to our centerMap function for centering on Campustown.
Declare another instance variable to store the targets map, then duplicate the above section of code to similarly prepare your targetsMap fragment. Make sure to change the findFragmentById parameter to operate on your new fragment. Youll then be able to see in the app that the map that appears for target mode setup is centered on Campustown just like the area mode setup one.
To keep track of the targets added so far, declare an instance variable to hold a list of Google Maps markers: a ListMarker. Initialize it to a new, empty list. Add some code to your new getMapAsync handler to make the users actions on the map add targets:
Register a longpress handler on the targets map by calling setOnMapLongClickListener. The handler receives a LatLng object specifying the point that was pressed. When that happens, create a marker at that position and add it to the list. Again see the implementation of the placeMarker function in GameActivity for how to place a marker on a map.
Likewise register a setOnMarkerClickListener handler, which is passed a Marker that the user clicked. Remove that marker from the map by calling its remove function and remove it from the targets list you declared as an instance variable.
In summary, youll want code like this inside the new getMapAsync:
targetMap.setOnMapLongClickListenerlocation
Code here runs whenever the user presses on the map.
location is the LatLng position where the user pressed.
1. Create a Google Maps Marker at the provided coordinates.
2. Add it to your targets list instance variable.
;
targetMap.setOnMarkerClickListenerclickedMarker
Code here runs whenever the user taps a marker.
clickedMarker is the Marker object the user clicked.
1. Remove the marker from the map with its remove function.
2. Remove it from your targets list.
return true;This makes Google Maps not pan the map again
;
After completing this task, testTargetMap will pass.
4.4. Invitees UI
When setting up a game, the user should be able to decide who is invited to the game and what roles they have. An invitee can be added by entering their email into a text box and pressing the Add button. All players users involved in or invited to the game, including the apps user, should be shown in a list with a dropdown to set the role, which defaults to observer. It should be possible to remove an inviteebut not the userby pressing the Remove button in their row.
To make this possible, we will need three new UI elements in activitynewgame.xml:
1.An email text box from the Text tab with ID newInviteeEmail to allow the user to enter an invitees email
2.A button with ID addInvitee to actually add the invitee to the list
3.An initially empty vertical LinearLayout with ID playersList to hold the list of players
To display one player entry, we have provided chunkinvitee.xml in the patch. You do not need to modify it, though you may customize it if you like. To store information about one player, we have provided the Invitee class. These will be used in NewGameActivity to make the invitees UI work.
Add an instance variable to NewGameActivity to store the list of players, that is, a ListInvitee. onCreate should initialize it to an empty list and then add an Invitee representing the user with the role of observer. You should create two helper functions to make the players list UI work:
4.4.1. Update Players UI
This function is responsible for repopulating the players list in the UI with the information stored in the players list instance variable.
First it should removeAllViews from the players LinearLayout. Then, much like in Checkpoint 2, it should go through the list variable and add a chunk to the LinearLayout for each player. We provided three views in that chunk:
inviteeEmail is a TextView whose text should be set to the players email.
inviteeTeam is a Spinner to let the user see and change the players role. Its selection should be set to the player objects team ID. When its selection is changed by the user, the player objects team ID should be updated to match. See our guide to the relevant spinner functions.
removeButton is a Button that removes the invitee. It should be gone for the first entry in the list the user, since the user shouldnt able to leave their own game. When clicked, it should remove the player object from the list variable and refresh the players UI list.
When accessing these views, make sure to call findViewById on the chunk you inflated inside your loop so that you get a reference to the view from the specific chunk youre currently building.
This function should be called by onCreate after adding the user to the players list variable so that the initial UI is set up and the user can choose their own role.
4.4.2. Add Invitee
This function should be called when the user presses the addInvitee button.
If the newInviteeEmail text box is not empty, a new invitee object with the entered email address and the role of observer should be added to the players list instance variable. The email text box should be made empty so that the user can enter the next invitee. Then the players UI should be updated by calling the other helper function so that the change is visible.
After completing this task, testInvitees will pass.
4.5. Game Creation API Request
The targets map and invitees UI can be done in either order. Once theyre both ready, the data they solicit from the user can be submitted to the server to create a multiplayer game.
When the Create Game button is clicked, build a JsonObject according to our API documentation for the game creation endpoint. You will need to include:
the configuration you made possible in Checkpoint 1, plus
data from the targets map if in target mode and
playersinvitees list.
Much of your Checkpoint 1 logic can still be used; youre just putting the data in a JSON object rather than an intent.
When your game JSON object is ready, POST it to the game creation endpoint. If the request succeeds, launch GameActivity passing the game ID from the response as the game extra of the intent, then finish NewGameActivity. If the request fails, show a toast like in the example web requests error handler that displays the errors message 7 so the user knows what went wrong.
Note that GameActivity should only be launched once the request completes, not immediately when the user presses the button. The Checkpoint 1 data no longer needs to go in the intent, though you can put it there if youd like the game to keep working in the meantime before Checkpoint 4 fixes everything.
After completing this task, testApiRequest will pass. Nice work! If youre up for a challenge, you can continue on to the extra credit section below.
4.6. Extra Credit: Target Presets
Challenge problem! This is extra credit because it takes a bit more work and tinkering. It can be done before the game creation API request, but you will need to have the targets map working first.
Many users wont want to spend a lot of time picking out enough targets for an interesting target mode game. To make it easier to add a set of targets, the app could have several suggested lists of targets and allow the user to add an entire suggested list at once.
Inside the target mode settings group, add a button with text Load Preset and ID loadPresetTargets. When it is clicked, fetch the list of presets from the server. When the request completes, create and show an AlertDialog to list the options. Refer to Androids AlertDialog guide for details.
This patch provides a chunkpresetslist.xml layout resource which you can inflate with a null parent 8 and pass to the dialog builders setView function. For each preset option, add a RadioButton inside the provided RadioGroup ID presetOptions, with the radio buttons text set to the presets name. This is the one place in the MP where you should create an individual view dynamically using new. The constructors for most Android views take a context, which can be the activity: this.
The alert dialogs positive button should be labeled Load; its negative button should be labeled Cancel. The dialog might look like this:
If the positive button Load is pressed with a preset selected, all existing targets should be removed and all the targets from the selected preset should be added. There are multiple ways to associate a preset with a radio button; you may find getTag and setTag helpful. If the user presses Cancel or presses Load without selecting a preset, do nothing and the dialog will be dismissed by default.
If you complete this task, testTargetPresetsextraCredit will pass and youll have earned 20 extra credit!
5. Grading
As always, 100 points is full credit on the checkpoint. But on MP3 there are 120 points available, broken down as follows:
15 points for the Target class
10 points for refactoring addLine and the uses of LineCrossDetector
15 points for the targets map
20 points for the invitees UI
20 points for the creategame web request
20 points of extra credit for the optional Load Preset feature
10 points for passing checkstyle inspection
10 points for submitting 9 code that earns 40 points by 8 PM on your early deadline day
If you missed a deadline in a previous checkpoint, doing the extra credit here is a great way to earn some of those points back!
Your app will be tested by Checkpoint3Test. Feel free to look through that classs code to see what the test suite tries to do with your app. Post on the forum for clarifications about what exactly is expected.
6. Cliffhanger
Because the game setup screen submits the game configuration to the server instead of passing it to the game activity, gameplay is probably pretty broken at the moment. In the next and final checkpoint, well finish the app by connecting the game activity to the server!
7. Cheating
By now you should be familiar with the cheating policies from the syllabus. Collaborating in a human language about how to approach the problems is encouraged, but sharing your code with anyone not currently on the course staff constitutes cheating.
Created 1112019
Updated 1112019
Commit 0a66b2cHistoryView
Built 111201913:14 EDT
Reviews
There are no reviews yet.