This shows you the differences between two versions of the page.
|
smd:laboratoare:06 [2019/04/08 20:57] adriana.draghici [Shared preferences] |
smd:laboratoare:06 [2021/05/20 21:30] (current) adriana.draghici [Shared preferences] |
||
|---|---|---|---|
| Line 18: | Line 18: | ||
| For sharing data to external apps, you can use one of the main Android components: [[https://developer.android.com/guide/topics/providers/content-provider-basics|ContentProviders]]. For files, this component is extended by the [[https://developer.android.com/reference/android/support/v4/content/FileProvider.html|FileProvider]]. | For sharing data to external apps, you can use one of the main Android components: [[https://developer.android.com/guide/topics/providers/content-provider-basics|ContentProviders]]. For files, this component is extended by the [[https://developer.android.com/reference/android/support/v4/content/FileProvider.html|FileProvider]]. | ||
| + | |||
| + | <note important>Android API Level 29 introduced important changes to how storage is accessed, driven by the need for increased privacy. | ||
| + | It provided **Scoped Storage** ([[https://developer.android.com/training/data-storage#scoped-storage|documentation]], [[https://www.raywenderlich.com/9577211-scoped-storage-in-android-10-getting-started|tutorial]]). Api Level 30 also introduced some updates related to storage, check [[https://developer.android.com/about/versions/11/privacy/storage|this link]] for some information about them. </note> | ||
| ====Internal Storage==== | ====Internal Storage==== | ||
| Line 45: | Line 48: | ||
| * Reading and writing to it requires the app to declare ''WRITE_EXTERNAL_STORAGE'' or ''READ_EXTERNAL_STORAGE'' **permissions** (declaring the write permission automatically grants the read permission as well). The next Android version, Q, deprecates these. | * Reading and writing to it requires the app to declare ''WRITE_EXTERNAL_STORAGE'' or ''READ_EXTERNAL_STORAGE'' **permissions** (declaring the write permission automatically grants the read permission as well). The next Android version, Q, deprecates these. | ||
| * accessing public external files requires the permissions | * accessing public external files requires the permissions | ||
| - | * accessing private external files do not requires the permissions from API Level 18 (Android 4.4 - KitKat). | + | * accessing private external files does not require the permissions from API Level 18 (Android 4.4 - KitKat). |
| - | * **__Security tips:__** | + | <note important>**__Security tips:__** |
| * validate the data read from external storage | * validate the data read from external storage | ||
| * encrypt sensitive data stored in external storage | * encrypt sensitive data stored in external storage | ||
| - | * decide wether the data should be persisted after the app is installed or not. | + | * decide whether the data should be persisted after the app is installed or not. |
| * ask for the permissions at runtime, preferably showing a custom message informing the user why do you need those permissions. | * ask for the permissions at runtime, preferably showing a custom message informing the user why do you need those permissions. | ||
| + | </note> | ||
| **How-to:** | **How-to:** | ||
| * Declare the permissions in the manifest (if needed) | * Declare the permissions in the manifest (if needed) | ||
| * Obtain the path using one of the following methods provided by the [[https://developer.android.com/reference/android/os/Environment.html|Environment]] class or Context classes: | * Obtain the path using one of the following methods provided by the [[https://developer.android.com/reference/android/os/Environment.html|Environment]] class or Context classes: | ||
| - | * [[https://developer.android.com/reference/android/os/Environment.html#getExternalStoragePublicDirectory(java.lang.String)|Environment.getExternalStoragePublicDirectory]] - returns the path to the public external directory | ||
| * [[https://developer.android.com/reference/android/content/Context.html#getExternalFilesDir(java.lang.String)|Context.getExternalFilesDir()]] - returns the path to the private external directory | * [[https://developer.android.com/reference/android/content/Context.html#getExternalFilesDir(java.lang.String)|Context.getExternalFilesDir()]] - returns the path to the private external directory | ||
| * [[https://developer.android.com/reference/android/content/Context.html#getExternalFilesDirs(java.lang.String)|Context.getExternalFilesDirs]] - returns an array of paths to the external directories (e.g. you can have two directories if an sd card is present). | * [[https://developer.android.com/reference/android/content/Context.html#getExternalFilesDirs(java.lang.String)|Context.getExternalFilesDirs]] - returns an array of paths to the external directories (e.g. you can have two directories if an sd card is present). | ||
| Line 63: | Line 65: | ||
| <code Java> | <code Java> | ||
| + | // Code working on versions < Q (Android10 aka API Level 29) | ||
| + | |||
| String path = Environment.getExternalStorageDirectory().toString(); | String path = Environment.getExternalStorageDirectory().toString(); | ||
| File file = new File(path, "image.png"); | File file = new File(path, "image.png"); | ||
| Line 75: | Line 79: | ||
| </code> | </code> | ||
| - | [[https://developer.android.com/training/data-storage/files.html#WriteExternalStorage|Code tutorial]] | + | From Android 10 you need to use ContentResolver like in these examples: [[https://www.raywenderlich.com/10217168-preparing-for-scoped-storage|Creating a new file]], [[https://stackoverflow.com/questions/56468539/getexternalstoragepublicdirectory-deprecated-in-android-q/57649669#57649669|ContentResolver SO thread]] |
| Line 89: | Line 93: | ||
| sharedPref = this.getSharedPreferences("mysettings", Context.MODE_PRIVATE); | sharedPref = this.getSharedPreferences("mysettings", Context.MODE_PRIVATE); | ||
| // or | // or | ||
| - | sharedPref = PreferenceManager.getDefaultSharedPreferences(context); | + | sharedPref = PreferenceManager.getDefaultSharedPreferences(context); //!!! recently deprecated in API level 29 |
| </code> | </code> | ||
| Line 125: | Line 129: | ||
| ==== Task 0 - Init ==== | ==== Task 0 - Init ==== | ||
| - | Create a new Android application project. Download the lab archive and use its content, or start with your own UI design. | + | Create a new Android application project. You can download the lab archive and use its layout, or start with your own UI design. |
| ==== Task 1 - Runtime permission check ==== | ==== Task 1 - Runtime permission check ==== | ||
| Line 141: | Line 145: | ||
| Implement a button listener which triggers an image download from a given URL and stores it on //external storage//. | Implement a button listener which triggers an image download from a given URL and stores it on //external storage//. | ||
| * Image url example: https://swarm.cs.pub.ro/~adriana/smd/android-kotlin.png | * Image url example: https://swarm.cs.pub.ro/~adriana/smd/android-kotlin.png | ||
| - | * Combine the knowledge from [[smd:laboratoare:04|Lab 4]] and [[smd:laboratoare:05|Lab 5]] for downloading content: use HttpsURLConnection inside of a Runnable or an AsyncTask. | + | * Combine the knowledge from [[smd:laboratoare:04|Lab 4]] and [[smd:laboratoare:05|Lab 5]] for downloading content: use HttpsURLConnection inside of a Runnable. |
| - | * You can handle the image using [[https://developer.android.com/reference/android/graphics/Bitmap|Bitmap]] and [[https://developer.android.com/reference/android/graphics/BitmapFactory|BitmapFactory]] as in the code snippets provided at the end of this section | + | * You can handle the image using [[https://developer.android.com/reference/android/graphics/Bitmap|Bitmap]] and [[https://developer.android.com/reference/android/graphics/BitmapFactory|BitmapFactory]] as in the code snippets provided at the [[https://ocw.cs.pub.ro/courses/smd/laboratoare/06#task_5_-_shared_preferences|end of this section]]. |
| * Obtain the path to the external storage using one of the methods described in the [[smd:laboratoare:06#external_storage|External Storage]] section. | * Obtain the path to the external storage using one of the methods described in the [[smd:laboratoare:06#external_storage|External Storage]] section. | ||
| - | * Log the path of the file | + | * Log the path of the file. |
| * Try to access the image from your phone's Files app or from command line (connect to your device using ''adb shell''). | * Try to access the image from your phone's Files app or from command line (connect to your device using ''adb shell''). | ||
| Line 156: | Line 160: | ||
| Provide the functionality from tasks 1 and 2 using internal storage instead of external. | Provide the functionality from tasks 1 and 2 using internal storage instead of external. | ||
| * You can choose to store the file using one of the methods described in the [[smd:laboratoare:06##internal_storage|Internal Storage]] section. | * You can choose to store the file using one of the methods described in the [[smd:laboratoare:06##internal_storage|Internal Storage]] section. | ||
| - | * Log the path of the file, try to access it from the command line. | + | * Log the path of the file, try to access it from the command line (adb shell). |
| ==== Task 5 - Shared preferences ==== | ==== Task 5 - Shared preferences ==== | ||
| Line 166: | Line 170: | ||
| * When the user selects an item from the spinner, store it as a shared preference. Use [[https://developer.android.com/reference/android/widget/AdapterView.html#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)|setOnItemSelectedListener]]. | * When the user selects an item from the spinner, store it as a shared preference. Use [[https://developer.android.com/reference/android/widget/AdapterView.html#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)|setOnItemSelectedListener]]. | ||
| * When the user clicks Download, use an external or internal path, depending on the value stored in the shared preferences. If the value is "Undefined", show a toast telling the user to choose a type of storage. | * When the user clicks Download, use an external or internal path, depending on the value stored in the shared preferences. If the value is "Undefined", show a toast telling the user to choose a type of storage. | ||
| + | |||
| + | <spoiler Example > | ||
| + | <code java> | ||
| + | private String getStorageType() { | ||
| + | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); | ||
| + | return sharedPref.getString(getString(R.string.storage_type_key), "undefined"); | ||
| + | } | ||
| + | |||
| + | private void storeStorageType(String type) | ||
| + | SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); | ||
| + | editor.putString(getString(R.string.storage_type_key), type); | ||
| + | editor.apply(); | ||
| + | } | ||
| + | |||
| + | private class StorageTypeSelectedListener implements AdapterView.OnItemSelectedListener { | ||
| + | |||
| + | @Override | ||
| + | public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { | ||
| + | String selectedItem = adapterView.getItemAtPosition(i).toString(); | ||
| + | Log.d(TAG, "Selected storage type: " + selectedItem); | ||
| + | storeStorageType(selectedItem); | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public void onNothingSelected(AdapterView<?> adapterView) { | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | </spoiler> | ||
| | | ||
| Image manipulation code snippets: | Image manipulation code snippets: | ||
| Line 192: | Line 225: | ||
| ===== Resources & Useful Links===== | ===== Resources & Useful Links===== | ||
| * [[https://developer.android.com/guide/topics/data/data-storage|Android Data Storage documentation]] | * [[https://developer.android.com/guide/topics/data/data-storage|Android Data Storage documentation]] | ||
| - | * [[https://developer.android.com/training/data-storage/files.html|Save files on device storage]] | + | * [[https://developer.android.com/about/versions/11/privacy/storage|Storage updates in Android 11]] |
| - | * {{lab6-skel.zip|}} | + | * {{lab6_skel.zip|}} |