Arvutiteaduse instituut
  1. Kursused
  2. 2020/21 sügis
  3. Mobiiliarvutus ja asjade internet (LTAT.06.009)
EN
Logi sisse

Mobiiliarvutus ja asjade internet 2020/21 sügis

  • Main
  • Lectures
  • Labs
  • Homeworks & Home Assignments
  • Quizes
  • Task submission
  • Extra Materials
  • Projects
    • Teams & Topics
    • Presentations & Report
    • Grading & Submission

Part 1: Recipe browser

  1. Download and open the base template project for this lab in Android Studio
    • The project contains a small dataset of food recipes in the file /assets/recipes.json
    • Secondly, there is a helper class ( Util.getRecipesFromJsonFile() ) to load the set of recipes from the json into a List of Recipe data class objects.
    • The Recipe data class has the following fields:
      • id: Int
      • name: String
      • description: String
  2. activity_main.xml contains a LinearLayout with the id layout_recipelist. The application fills the LinearLayout with buttons corresponding to names of recipes in the function displayRecipes().
  • Update the code: for each button, create a click listener, make the click listener call another function openDetailsActivity() (you have to create this new function, you can leave it empty right now)
  1. Create a 2nd Activity (New -> Activity -> Activity (blank) ), call it "DetailsActivity"
    • Its XML layout should have 2 TextViews:
      • for the recipe name
      • for the recipe description
  2. Update the openDetailsActivity() function in MainActivity
    • It should create a new intent to launch DetailsActivity
      • Intent(this, DetailsActivity::class.java)
    • Add the name of the recipe to the intent as an Extra (use intent.putExtra( ) ). For this, you should define a new String argument to openDetailsActivity()
    • Launch the 2nd activity with your intent using startActivity()
  3. Test your application, you should be able to navigate to the 2nd activity, but right now it does not show the recipe name.
    • In onCreate() of DetailsActivity, acquire the recipe name using intent.getStringExtra( .. )
    • Update the recipe name TextView so that it displays the value.

Let's also display the recipe description in DetailsActivity. We could pass the description as an Extra from MainActivity again, but instead, we are aiming to keep the data sent in Intent Extras lightweight.
Instead, let's only pass the ID of the recipe in the Intent, and make the 2nd activity handle the loading of the dataset and the correct sub-items in the dataset based on the ID only.

  1. Update the openDetailsActivity() so that it instead of the recipe name, it uses the recipe's ID
    • Pass the ID as an Intent extra to DetailsActivity
  2. Update DetailsActivity:
    • Using the ID, find the right Recipe object in the Recipe List.
      • You can use recipes?.find { it.id == id } , for example. ( Find is an alias to firstOrNull(), it uses a lambda predicate to find the first matching object in a collection or return null if none is found ).
    • Now that you have the Recipe object, set the values of the name and description TextViews accordingly.

Part 2: Adding a Rating System to our Recipe browser

  1. Update the DetailsActivy XML UI, adding
    • A RatingBar widget
    • A button with the text "Save"
  • Add a click handler to the "Save" button, make it call a function named "getRatingAndClose()" and implement it.
  • getRatingAndClose( .. ) should:
    • Get the value of the rating bar as an integer.
    • Create an empty intent object ( val resultIntent = Intent() ), put the recipe ID and rating bar value into the intent Extras.
    • Finish the activity with a result
      • setResult(resultCode, resultIntent) and finish()
  1. Now inside of MainActivity, let's handle the resulting rating score from DetailsActivity.
  • Replace the startActivity(..) with startActivityForResult(..)
    • The request code argument is something you have to choose, it is used to differentiate different activities returning to the current activity (imagine if for each recipe, you could instead of the description, open another activity about their author).
    • Override onActivityResult( .. ):
      • Obtain the score value and recipe ID from the attached Intent data object.
      • Then, create and call a function @@updateButtonColor(rating: Int, recipeId: Int)
    • We want to update the recipe buttons color based on the rating. We need to update our Button creation so that we can find the right button based on the recipe ID.
      • Specify a tag for each button when creating it, e.g. myButton.tag = recipe.id
      • Now, in updateButtonColor(..), you can find the button with: layout_recipe_list.findViewWithTag<Button>(resultId)
  • Now we can update the color. Let's first add some color resources, update colors.xml to include:
    <color name="color_rating_1">#850000</color>
    <color name="color_rating_2">#F57C00</color>
    <color name="color_rating_3">#FBC02D</color>
    <color name="color_rating_4">#AFB42B</color>
    <color name="color_rating_5">#388E3C</color>
  • in onActivityResult, set the color based on the rating/score like so:
        // find resource id by using its name
        val colorId = resources.getIdentifier("color_rating_$score", "color", packageName)
        button.setBackgroundColor(getColor(colorId))

Part 3: Fragments

Let's recreate the above app using fragments. Instead of starting a new Activity, all of our UI will be inside one MainActivity. When created, the activity will first add a fragment that displays the list of recipe titles. When a title inside the fragment is clicked, the activity then replaces the list fragment with a details fragment within its layout.

  • Fetch another fresh copy of the lab base project, and open it as a new project.
  • Make the MainActivity XML consist of one FrameLayout with id fragment_container. We will place fragments inside of the FrameLayout.
  • Create a new XML layout file called fragment_recipelist. This will be the UI describing the fragment for the recipe list.
    • It should have a vertical LinearLayout, set the id to layout_recipelist
  • Create a new Kotlin class called RecipeListFragment and set the contents to be the following:

Show Code

package com.example.lab3

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class RecipeListFragment: Fragment() {

       override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_recipelist, container, false)
        // TODO: add buttons to the view
        return view
    }

}

Now, let's add the recipe names to the RecipeListFragment programmatically

  • in onCreateView() of RecipeListFragment.kt, add buttons showing the recipe titles to the LinearLayout layout_recipelist, similar to the way we did in Part 1 (displayRecipes() )
    • Note that now you need to refer to all of the XML-defined UI elements through the inflated view object, e.g. view.layout_recipelist.addView(button)
  • Set the button titles, but don't set a click listener yet.

We have programatically designed the look of the RecipeListFragment , but we have not specified for MainActivity to show it yet.

  • In MainActivity.kt, create a function called "displayRecipeListFragment":
    fun displayRecipeListFragment() {
        val listFragment = RecipeListFragment()

        val fragmentManager = supportFragmentManager
        val transaction = fragmentManager.beginTransaction()
        transaction
            .replace(R.id.fragment_container, listFragment, "listFragmentTag")
            .commit()
    }
  • this function sets RecipeListFragment to be displayed in the fragment_container FrameLayout which was defined in MainActivity. It replaces any previous fragment contained in it.
  • Now, call the above function inside onCreate() of MainActivity
  • Test the application and make sure MainActivity is displaying ListFragment

DetailsFragment

Now we will create the recipe details fragment. When a list button is clicked, we add it to the fragment_container with a transcation, similar to above.

  • Create another Fragment called RecipeDetailsFragment. The UI XML should have a Layout of your choice, which contains a TextView to show the recipe title and a button to close the Details view.
  • Add a 2nd function to MainActivity called "displayDetailsFragment", similar to the displayRecipeListFragment() above.
    • It should take as argument the ID of a recipe to be shown
  • Add a click handler to the buttons created in RecipeListFragment, so that clicking on them calls the displayDetailsFragment function of the parent MainActivity.
    • To access your method defined in MainActivity from a Fragment:
    val mainActivity = activity as MainActivity
    mainActivity.displayDetailsFragment()
  • Verify that clicking on an item in ListFragment makes the Activity replace the ListFragment with a new DetailsFragment
  • Let's update the displayDetailsFragment() method to pass extra arguments to the fragment (the recipe title)
    • You can do this with fragment.setArguments()
    val arguments = Bundle()
    arguments.putString("name", title)
    detailsFragment.arguments = arguments
  • In the Fragment onCreateView, you can access those with:
    val bundle =  this.getArguments()
    val title = bundle?.getString("name")
  • Finally, programmatically add onClickBehaviour to the save button in DetailsFragment XML.
    • Don't forget that to get a reference to a XML-defined button inside fragment onCreateView(..) function, you should use view.my_button_id not just my_button_id !
  • Clicking this button should call displayListFragment()
  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused