Mini project: Recipes app with Camera
Deadline: 30.10.2022
This task is to be solved with a co-student (both should submit the same source-code).
In this mini-project, we shall create a more complete Recipes App, involving local database storage and camera-based images.
The app allows the user to :
- See a list of recipes when launching the app (5 pts)
- Each item in the list has a text title and may also show an image thumbnail.
- The user can click on a recipe to see its details (3 pts)
- Details must include:
- A Title, recipe steps, an Image* (*if the recipe has one)
- Details must include:
- User can create new recipes (3 pts)
- Clicking on a separate button in the recipe list takes the user to a new screen view for creating the recipe
- They must be able to enter a name and recipe instructions (both always required).
- User has the option to attach a picture while creating a Recipe (4 pts)
- A button should allow the user to use the systems Camera app to take a picture.
- After taking a picture, the Image should be visible in the New Recipe view.
- The user can finalize creating the recipe by clicking a button, which returns them to the lists. where the new recipe should appear (including the image, if the user took one)
Technical requirements:
- The recipes should be stored (and queried) using a Room DB
- For images, you should only store the file path / file URI in the DB. The actual Image file is stored on the filesystem.
- The list should be implemented using RecyclerView
- The Photo taking should be implemented using Intents (see below for guideline)
You should aim for a stable and consistent user experience!
- The application should not crash under typical usage and circumstances (e.g. when user cancels some action or leaves some data not entered etc etc).
- For an example solution and some more tips, check out the ending of the Lab 6 video recording
How to implement the camera/picture feature.
General strategy is the following. A more detailed guide is below.
- Define an Intent for taking a picture with the Camera app.
- Attach to this intent a new Files path (you define it). The Camera app will save the picture there.
- Launch the Intent
- Once you get a result back from launching the intent, check that the result was OK, then you can use the File path you defined earlier, to load a Bitmap, store this path in the Room DB, etc.
Detailed Guide
To make this work, our App needs to define a ContentProvider which the Camera app will use to store files into our apps file storage space.
- For this, you need to define a content provider in the AndroidManifest.xml:
<!-- Below code has to go inside of application tag, similar to activity --> <provider android:name="androidx.core.content.FileProvider" android:authorities="ee.ut.cs.recipeappp.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider" /> </provider>
- The above definition links to a XML resource file, you need to create it
/res/xml/fileprovider.xml
, its contents should be:
<?xml version="1.0" encoding="utf-8"?> <paths> <!-- The external-files-path DOES NOT require external storage permissions. --> <external-files-path name="images" path="Pictures" /> </paths>
- Next, let's see how we can to define the File-s location which we should pass to the Intent.
Defining the Intent looks something like this:
// Define the file using a custom function getPhotoFile() (see below)
val photoFile = getPhotoFile("myFileName.jpg")
val fileUri = FileProvider.getUriForFile(requireContext(), "ee.ut.cs.recipeappp.fileprovider", photoFile)
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
launcher.launch(takePictureIntent) // use a registerForActivityResult(.. ) launcher
- The above code relies on this function:
/** * Returns the File for a photo stored on disk given the fileName. * Creating the storage directory if it does not exist:*/ fun getPhotoFile(fileName: String): File { // Get safe storage directory for photos. Use `getExternalFilesDir` on Context to access package-specific directories. // This way, we don't need to request external read/write runtime permissions. val mediaStorageDir: File = context?.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!! if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()) { Log.d(APP_TAG, "failed to create directory") } return File(mediaStorageDir.path + File.separator + fileName) }
- If the above Intent is launched and picture is taken, then we can try to load the image once the launchers callback is invoked.
- The load a Bitmap from a File path you can:
val bmp = BitmapFactory.decodeFile(pictureFilePath)
More tips
- Store the File path as a String (e.g. using File-s absolutePath method).
- Don't forget to scale down the image before showing it in UI.
- Because we want 1 picture per recipe, you have to make sure you are creating unique filenames for each new recipe. One strategy is to use of timestamps in the filename to ensure uniqueness.