0

I want my app to automatically load data from a file at startup & then display a summary of this data for the user. I thought this would be simple, but...

I almost had it working, but found out that it would be tricky putting the data file on the emulated Android provided by Android Studio. So I put a basic version of the file in the package as an asset. My code checked for the file, and if not found, copies the file from assets to the phone storage. So far so good, but then I realized that this didn't really meet the requirements, because the user couldn't customize the file (through another app or by putting a new version of the file on her phone).

I found I needed to use "external" storage (which isn't really external, it's merely shared / public storage). So I used getExternalStoragePublicDirectory() to get access to the Documents folder. In the part of my code which copies the asset file to the Documents folder, I get "java.io.IOException: No such file or directory" when I try to create the target file.

This really threw me for a while, since I had preceded it with mkdirs() to ensure the folder(s) existed and I was trying to create the file in the first place.

After lots of poking around on S/O, it seems my problem may be that I don't have permissions to read / write in the Documents folder. So this is where I can't get my head around how this is supposed to work.

In my Activity's OnCreate(), I want to load the data from my file. But I need to check the permissions first. This is guaranteed to fail the first time, because the user has never granted my app this permission before.

So then I need to request the permission. But this means I can no longer load my data in the OnCreate() method, as I will get the response from the user asynchronously, in a callback method (onRequestPermissionsResult).

If the user says 'No' to my request, I need to make sure the app quits gracefully. If she says 'Yes', then... what? At this point I'm in a callback and my OnCreate() method is no longer running. How can I get back to setting up my Activity?

If I load the data file in the callback method, that only works for the initial case where the user must give permission. But after that, my app (most likely) will not lose permission, so my app will not need to ask for it. The callback will never be executed in this case. So my data file must be loaded... where?

But all of this only happens if the user is running Android 6.0 or higher. If it's an earlier version of Android, then my app can load the data in my Activity's OnCreate() method.

Oh... my head hurts!

How is all of this supposed to work? I don't need a link to the relevant methods. I need a conceptual model - a diagram would really help!


Here's the solution in a diagram: Permissions Conceptual Diagram

Todd Hoatson
  • 123
  • 2
  • 18

1 Answers1

0

How can I get back to setting up my Activity?

Move the "setting up my Activity" code that depends on file I/O to a separate method. In onCreate(), check to see if you have the permission, and if you do, call that method to set up your activity. If not, call requestPermissions(). In onRequestPermissionsResult(), if you now have the permission, call that method to set up your activity.

See this sample app, which performs disk I/O from external storage using runtime permissions. The permission-handling logic is isolated in an AbstractPermissionsActivity, leaving the main activity class to handle the real business logic.

If it's an earlier version of Android, then my app can load the data in my Activity's OnCreate() method.

Well, you should be doing this disk I/O on a background thread. You can kick off that I/O from onCreate().

If you are using ContextCompat and ActivityCompat for checking and requesting permissions, respectively, your code will "do the right thing" on older devices, as those classes do the version checks for you.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • **"Well, you should be doing this disk I/O on a background thread."** @CommonsWare, how would that help??? My app can't display anything until it finishes loading that data. So the main activity will be blocking, waiting for that thread to finish. No difference from the user's perspective, right? – Todd Hoatson Jun 27 '17 at 23:56
  • @ToddHoatson: "how would that help?" -- it would avoid freezing your UI while the I/O is going on. "My app can't display anything until it finishes loading that data" -- portions of your app (e.g., help screens) should be accessible while the data is loading. For the portions that are inaccessible, use loading indicators or similar approaches. – CommonsWare Jun 28 '17 at 00:00
  • I've looked at your sample code. It's short, but fairly opaque. I get that you made your class an extension of Activity, because you need to be able to pass _this_ to the permissions methods. (Which is an odd thing about Android's implementation, since permission is not granted per activity, but rather per app.) I like that you can handle multiple permissions at once. – Todd Hoatson Jun 28 '17 at 03:29
  • But it seems really odd that you have the OnCreate() in your permissions class, and have removed it from MainActivity. **Maybe you can comment on that choice.** I would have thought you would want the user to use your class without having to alter it. But I would need to put some of my OnCreate stuff (eg. setting up layouts) in your AbstractPermissionActivity.OnCreate(). **Also, it's not clear to me what you're doing with the InstanceState. Can you explain that?** I'm still struggling to reconcile all this complexity with the simple file i/o I'm trying to do... – Todd Hoatson Jun 28 '17 at 03:29
  • @ToddHoatson: "but fairly opaque" -- it's covered in [a book](https://commonsware.com/Android), along with lots of other topics. "Maybe you can comment on that choice" -- there is nothing stopping a subclass from overriding `onCreate()`, for things that do not depend on the permissions. "it's not clear to me what you're doing with the InstanceState" -- if, while the permission dialog is up, the user rotates the screen or triggers another configuration change, the activity will be recreated and, by default, would raise a second dialog with the first already on the screen. This is bad UX. – CommonsWare Jun 28 '17 at 10:57
  • InstanceState: **"if, while the permission dialog is up, the user rotates the screen or triggers another configuration change, the activity will be recreated and, by default, would raise a second dialog with the first already on the screen."** OK, cool. – Todd Hoatson Jun 28 '17 at 16:16
  • **"...it's covered in a book, along with lots of other topics."** I've already seen your book, and I'm tempted. But I noticed on your page you wrote: **"the core chapters are designed to be read in sequence"** Since you also mention that the core chapters are ~800 pages, I wasn't sure how many hundreds of pages I would need to read to get to this file stuff... So thanks for responding here! – Todd Hoatson Jun 28 '17 at 16:22
  • **"...there is nothing stopping a subclass from overriding onCreate()"** So I could do this (sorry can't do newlines): `public class Summary extends AbstractPermissionActivity' and in onCreate() I could have: 'super.onCreate(savedInstanceState); setContentView(R.layout.activity_summary); RecyclerView _rv = (RecyclerView) findViewById(R.id.rvTaskList); _li = getLayoutInflater(); _rv.setLayoutManager(new LinearLayoutManager(this)); TaskItemAdapter adapter = new TaskItemAdapter(); _rv.setAdapter(adapter);` – Todd Hoatson Jun 28 '17 at 16:32
  • @ToddHoatson: Assuming that `TaskItemAdapter` is happy with having no data at the outset, that seems fine. "I wasn't sure how many hundreds of pages I would need to read to get to this file stuff" -- by that, I am drawing a contrast to the remaining chapters ("the trails"), which are designed for you to read when you need to learn about specific topics covered in those chapters. That phrase also assumes that you are starting from scratch in terms of Android knowledge. The list of chapters on that page shows, at a high level, what is covered where. – CommonsWare Jun 28 '17 at 16:32
  • OK, so I should probably move `_rv.setAdapter(adapter);` to an onReady() method, right? `super.OnCreate()` in this case would call AbstractPermissionActivity.OnCreate(), which would then call AppCompatActivity.OnCreate(). So no problem, right? – Todd Hoatson Jun 28 '17 at 16:39
  • @ToddHoatson: It is very difficult to visualize code pasted into a roster of SO comments. I see no obvious problems in what you are describing. If you run into problems, ask a fresh Stack Overflow question, with a [mcve] and description of those problems. – CommonsWare Jun 28 '17 at 16:43