Skip to content

Latest commit

 

History

History
513 lines (438 loc) · 17.7 KB

11-1-M-Develop-Android-Part-2.adoc

File metadata and controls

513 lines (438 loc) · 17.7 KB

Developing for Android (Part 2)

Note
You can use the https://eventregistration-backend-123.herokuapp.com backend URL for the event registration example in the HttpUtils class.

As a next step, we extend the view and its behavior. Key interactions of our application added in this phase are the following:

  1. What to do when the application is launched? (onCreate())

  2. What to do when application data is updated? (refreshLists())

  3. What to do when a button is clicked? (addEvent(), and register())

The expected layout of the application:

android studio main layout

Create helper classes

  1. Create the classes included in the next two steps within the ca.mcgill.ecse321.eventregistration package

  2. Create a new class called DatePickerFragment

    Note
    Import missing classes with Alt+Enter. If the import option offers multiple classes, choose the ones that are not deprecated.
    public class DatePickerFragment extends DialogFragment
            implements DatePickerDialog.OnDateSetListener {
    
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            // Use the current date as the default date in the picker
            final Calendar c = Calendar.getInstance();
            int year = c.get(Calendar.YEAR);
            int month = c.get(Calendar.MONTH);
            int day = c.get(Calendar.DAY_OF_MONTH);
    
            // Parse the existing time from the arguments
            Bundle args = getArguments();
            if (args != null) {
                year = args.getInt("year");
                month = args.getInt("month");
                day = args.getInt("day");
            }
    
            // Create a new instance of DatePickerDialog and return it
            return new DatePickerDialog(getActivity(), this, year, month, day);
        }
    
        public void onDateSet(DatePicker view, int year, int month, int day) {
            MainActivity myActivity = (MainActivity)getActivity();
            myActivity.setDate(getArguments().getInt("id"), day, month, year);
        }
    }
  3. Create a new class called TimePickerFragment

    Note
    The method setTime is missing from the MainActivity class and will be added later (i.e., the error message which complains that this method is missing is normal)
    public class TimePickerFragment extends DialogFragment
            implements TimePickerDialog.OnTimeSetListener {
    
        String label;
    
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            int hour = 0;
            int minute = 0;
    
            // Parse the existing time from the arguments
            Bundle args = getArguments();
            if (args != null) {
                hour = args.getInt("hour");
                minute = args.getInt("minute");
            }
    
            // Create a new instance of TimePickerDialog and return it
            return new TimePickerDialog(getActivity(), this, hour, minute,
                    DateFormat.is24HourFormat(getActivity()));
        }
    
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            MainActivity myActivity = (MainActivity)getActivity();
            myActivity.setTime(getArguments().getInt("id"), hourOfDay, minute);
        }
    }
  4. Add the following helper methods within the MainActivity class to support date and time pickers

    private Bundle getTimeFromLabel(String text) {
        Bundle rtn = new Bundle();
        String comps[] = text.toString().split(":");
        int hour = 12;
        int minute = 0;
    
        if (comps.length == 2) {
            hour = Integer.parseInt(comps[0]);
            minute = Integer.parseInt(comps[1]);
        }
    
        rtn.putInt("hour", hour);
        rtn.putInt("minute", minute);
    
        return rtn;
    }
    
    private Bundle getDateFromLabel(String text) {
        Bundle rtn = new Bundle();
        String comps[] = text.toString().split("-");
        int day = 1;
        int month = 1;
        int year = 1;
    
        if (comps.length == 3) {
            day = Integer.parseInt(comps[0]);
            month = Integer.parseInt(comps[1]);
            year = Integer.parseInt(comps[2]);
        }
    
        rtn.putInt("day", day);
        rtn.putInt("month", month-1);
        rtn.putInt("year", year);
    
        return rtn;
    }
    
    public void showTimePickerDialog(View v) {
        TextView tf = (TextView) v;
        Bundle args = getTimeFromLabel(tf.getText().toString());
        args.putInt("id", v.getId());
    
        TimePickerFragment newFragment = new TimePickerFragment();
        newFragment.setArguments(args);
        newFragment.show(getSupportFragmentManager(), "timePicker");
    }
    
    public void showDatePickerDialog(View v) {
        TextView tf = (TextView) v;
        Bundle args = getDateFromLabel(tf.getText().toString());
        args.putInt("id", v.getId());
    
        DatePickerFragment newFragment = new DatePickerFragment();
        newFragment.setArguments(args);
        newFragment.show(getSupportFragmentManager(), "datePicker");
    }
    
    public void setTime(int id, int h, int m) {
        TextView tv = (TextView) findViewById(id);
        tv.setText(String.format("%02d:%02d", h, m));
    }
    
    public void setDate(int id, int d, int m, int y) {
        TextView tv = (TextView) findViewById(id);
        tv.setText(String.format("%02d-%02d-%04d", d, m + 1, y));
    }

Update view definition

  1. The corresponding (but partly incomplete) view definition in the content_main.xml file is the following:

    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/error"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:visibility="gone"
            android:text=""
            android:textColor="@color/colorAccent"/>
    
        <LinearLayout
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:orientation="vertical">
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:orientation="vertical">
                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent">
                    <TextView
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        android:text="@string/personspinner_label"/>
                    <Spinner
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        android:layout_gravity="end"
                        android:id="@+id/personspinner"/>
                </LinearLayout>
                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent">
                    <TextView
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        android:text="@string/eventspinner_label"/>
                    <Spinner
                        android:id="@+id/eventspinner"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="end"
                        android:layout_margin="0dp"/>
                </LinearLayout>
            </LinearLayout>
            <!-- TODO add a Register and Refresh Lists buttons here -->
        </LinearLayout>
    
        <View
            android:layout_height="2dp"
            android:layout_width="fill_parent"
            android:background="#16552e"/>
    
        <LinearLayout
            android:orientation="vertical"
            android:layout_height="wrap_content"
            android:layout_width="match_parent">
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/newperson_name"
                android:hint="@string/newperson_hint"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="end"
                android:text="@string/newperson_button"
                android:onClick="addPerson"/>
        </LinearLayout>
    
        <View
            android:layout_height="2dp"
            android:layout_width="fill_parent"
            android:background="#16552e"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <EditText android:id="@+id/newevent_name"
                android:layout_height="wrap_content"
                android:layout_width="fill_parent"
                android:hint="@string/newevent_hint"/>
            <LinearLayout
                android:orientation="horizontal"
                android:layout_height="wrap_content"
                android:layout_width="match_parent">
                <TextView
                    android:layout_height="wrap_content"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:text="@string/newevent_date_label"/>
                <TextView
                    android:layout_height="wrap_content"
                    android:layout_width="wrap_content"
                    android:text="@string/newevent_date_first"
                    android:layout_gravity="end"
                    android:id="@+id/newevent_date"
                    android:onClick="showDatePickerDialog"/>
            </LinearLayout>
            <LinearLayout
                android:orientation="horizontal"
                android:layout_height="wrap_content"
                android:layout_width="match_parent">
                <TextView
                    android:layout_height="wrap_content"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:text="@string/starttime_label"/>
                <TextView
                    android:layout_height="wrap_content"
                    android:layout_width="wrap_content"
                    android:text="@string/starttime_first"
                    android:layout_gravity="end"
                    android:id="@+id/starttime"
                    android:onClick="showTimePickerDialog"/>
            </LinearLayout>
            <!-- TODO add a label and a time picker for event end time -->
            <!-- TODO add Add Event button here -->
        </LinearLayout>
    </LinearLayout>
  2. The missing string definitions go in the res/values/strings.xml resource

    <resources>
        <string name="app_name">EventRegistration-Android</string>
        <string name="action_settings">Settings</string>
        <string name="newperson_hint">Who?</string>
        <string name="newperson_button">Add Person</string>
        <string name="newevent_date_label">Date?</string>
        <string name="personspinner_label">Person?</string>
        <string name="eventspinner_label">Event?</string>
        <string name="starttime_label">Start time?</string>
        <string name="newevent_date_first">01-07-2019</string>
        <string name="starttime_first">10:00</string>
        <string name="newevent_hint">Event Name?</string>
    </resources>
    • TODO: add a Register button to allow registering a selected person to a selected event (call the register() method when clicked - this is to be implemented in the upcoming steps)

    • TODO: add a Refresh Lists button that refreshes the contents of the event and person spinners (call the refreshLists() method when clicked)

    • TODO: add a label with text End? below the Start? label

    • TODO: add a time picker to select the end time of a new event

    • TODO: add an Add Event button to allow creating new events from the user interface (call the addEvent() method when clicked - this is to be implemented in the upcoming steps)

Initialization on application launch

  1. Open the MainActivity.java file.

  2. Add a few new attributes to the beginning of the class as helpers for persistence and error handling.

    public class MainActivity extends AppCompatActivity {
      private String error = null;
      // APPEND NEW CONTENT STARTING FROM HERE
      private List<String> personNames = new ArrayList<>();
      private ArrayAdapter<String> personAdapter;
      private List<String> eventNames = new ArrayList<>();
      private ArrayAdapter<String> eventAdapter;
    
      //...
    }
    • Import missing classes (e.g. use Alt+Enter)

  3. Add code to initialize the application with data from the server in the onCreate() method (after the auto-generated code).

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      // ...
      // INSERT TO END OF THE METHOD
      // Add adapters to spinner lists and refresh spinner content
      Spinner personSpinner = (Spinner) findViewById(R.id.personspinner);
      Spinner eventSpinner = (Spinner) findViewById(R.id.eventspinner);
    
      personAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, personNames);
      personAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
      personSpinner.setAdapter(personAdapter);
    
      eventAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, eventNames);
      eventAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
      eventSpinner.setAdapter(eventAdapter);
    
      // Get initial content for spinners
      refreshLists(this.getCurrentFocus());
    }

At this point the refreshLists() method is missing, this is to be implemented in the upcoming steps.

Reactions to updated data

  1. Create the missing new method refreshLists() which seeks for the event and person spinners and sets their content according to the data retrieved from the server

    public void refreshLists(View view) {
        refreshList(personAdapter ,personNames, "people");
        refreshList(eventAdapter, eventNames, "events");
    }
    
    private void refreshList(final ArrayAdapter<String> adapter, final List<String> names, final String restFunctionName) {
        HttpUtils.get(restFunctionName, new RequestParams(), new JsonHttpResponseHandler() {
    
            @Override
            public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
                names.clear();
                names.add("Please select...");
                for( int i = 0; i < response.length(); i++){
                    try {
                        names.add(response.getJSONObject(i).getString("name"));
                    } catch (Exception e) {
                        error += e.getMessage();
                    }
                    refreshErrorMessage();
                }
                adapter.notifyDataSetChanged();
            }
    
            @Override
            public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
                try {
                    error += errorResponse.get("message").toString();
                } catch (JSONException e) {
                    error += e.getMessage();
                }
                refreshErrorMessage();
            }
        });
    }
  2. Implement the addEvent() method

    public void addEvent(View v) {
        // start time
        TextView tv = (TextView) findViewById(R.id.starttime);
        String text = tv.getText().toString();
        String comps[] = text.split(":");
    
        int startHours = Integer.parseInt(comps[0]);
        int startMinutes = Integer.parseInt(comps[1]);
    
        // TODO get end time
    
        // date
        tv = (TextView) findViewById(R.id.newevent_date);
        text = tv.getText().toString();
        comps = text.split("-");
    
        int year = Integer.parseInt(comps[2]);
        int month = Integer.parseInt(comps[1]);
        int day = Integer.parseInt(comps[0]);
    
        // name
        tv = (TextView) findViewById(R.id.newevent_name);
        String name = tv.getText().toString();
    
        // Reminder: calling the service looks like this:
        // https://eventregistration-backend-123.herokuapp.com/events/testEvent?date=2013-10-23&startTime=00:00&endTime=23:59
    
        RequestParams rp = new RequestParams();
    
        NumberFormat formatter = new DecimalFormat("00");
        rp.add("date", year + "-" + formatter.format(month) + "-" + formatter.format(day));
        rp.add("startTime", formatter.format(startHours) + ":" + formatter.format(startMinutes));
        // TODO add end time as parameter
    
        HttpUtils.post("events/" + name, rp, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
                refreshErrorMessage();
                ((TextView) findViewById(R.id.newevent_name)).setText("");
            }
    
            @Override
            public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
                try {
                    error += errorResponse.get("message").toString();
                } catch (JSONException e) {
                    error += e.getMessage();
                }
                refreshErrorMessage();
            }
        });
    }
    • TODO: get the end time of the new event

    • TODO: supply the end time to the REST request as an additional parameter

  3. Implement the register() method

    public void register(View v) {
    
        Spinner partSpinner = (Spinner) findViewById(R.id.personspinner);
        Spinner eventSpinner = (Spinner) findViewById(R.id.eventspinner);
    
        error = "";
    
        // TODO issue an HTTP POST here
        // Reminder: calling the service looks like this:
        // https://eventregistration-backend-123.herokuapp.com/register?person=testPreson&event=testEvent
    
    
        // Set back the spinners to the initial state after posting the request
        partSpinner.setSelection(0);
        eventSpinner.setSelection(0);
    
        refreshErrorMessage();
    }
    • TODO: implement the HTTP POST part of the register() method on your own