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|}} |