Office Hours — Today, April 29

Tuesday, April 27

Apr 29
8:20 AM
Mark M.
has entered the room
Mark M.
turned on guest access
8:35 AM
Jan
has entered the room
Jan
View paste
I've been tasked to converting to flow.
 private val isConnectedBroadcastChannel = ConflatedBroadcastChannel(false)
 private val isConnectedChannel get() = isConnectedBroadcastChannel.openSubscription()
I was asked to only convert isConnectedChannel because it uses operators (an extension). Would I also need to convert the isConnectedBroadcastChannel?
8:40 AM
Mark M.
sorry, but I do not understand the question
8:40 AM
Mark M.
OK, so, isConnectedChannel is a ReceiveChannel
so, you are supposed to switch isConnectedChannel to be a Flow instead?
if so, that should just be:
Jan
Yes. I'm not sure if the original ConflatedBroadcastChannel with the handle isConnectedBroadcastChannel needs the conversion in order to get a flow on the openSubscription call.
Mark M.
private val isConnectedChannel get() = isConnectedBroadcastChannel.asFlow()
and isConnectedBroadcastChannel could, in principle, stay as it is
whether that is the right answer overall (versus changing isConnectedBroadcastChannel to be a SharedFlow or something), I cannot say
8:45 AM
Jan
Thanks.
No more questions but I'll stay in the room in case something comes up.
Mark M.
ok!
9:00 AM
Scott W.
has entered the room
Mark M.
hello, Scott!
how can I help you today?
Scott W.
hey
I was just signing in to see when the next office hours were. Didn't expect it to be live
I have a question about storage but I haven't structured the question yet
Mark M.
sounds fine -- Jan is lurking at the moment as well, so if either of you come up with a question, go ahead and ask!
Scott W.
I've got a device and I'm browsing using Android Studio's Device File Explorer. I'm in storage/emulated/... and it says permission denied.
but I think I'm storing pictures here.
the pictures should be visible to other apps on the device. Google Photos for example
Mark M.
what OS version?
Scott W.
8
api 26
9:05 AM
Mark M.
use /storage/self/primary
Scott W.
I think I'm using getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
Mark M.
in code, yes -- sorry, I was referring to Device File Explorer
Scott W.
/storage/self/primary is not where my pictures are ending up
I'll try this again with the debugger and get back to you
Mark M.
sorry, I meant that as a counterpart to the /storage/emulated that you tried and got "permission denied"
/storage/self/primary/Pictures should get you to DIRECTORY_PICTURES, at least for the current Android account
Jan
I have a question when Scott is done.
Scott W.
I'm done for now
Mark M.
Jan: go right ahead!
9:10 AM
Jan
View paste
Is there a way in flow to call a close handler? I have this extension and just wondered if I needed to convert to flow or there was another way:
internal fun <E> ReceiveChannel<E>.withCloseHandler(
    context: CoroutineContext = Dispatchers.Unconfined,
    handler: (Throwable?) -> Unit
): ReceiveChannel<E> = GlobalScope.produce(context) {
    invokeOnClose(handler)
    consumeEach { send(it) }
}
Mark M.
that doesn't really exist as a concept for plain Flow, as there is only one Flow source and only one consumer, and so they each know already when things get closed
Jan
The asFlow is working well. Now I just need a way to call the close handler.
Mark M.
I'll be honest, that extension function is rather scary
9:15 AM
Jan
The close is trying to turn off bluetooth notifications when channel closes. But I guess if flow closes, there wouldn't be any notifications coming in. Is that what you are saying?
*THe close handler being passed in is turning off notifications enable.
Mark M.
well, I can't really answer that, as I lack context
but, it feels to me like all of the Bluetooth stuff should be consolidated in whatever it is that is creating the flow of events that come out of the Bluetooth communications
Jan
We will probably end up doing that but time estimate is rather large - almost 10 channels to convert over to flow.
9:20 AM
Scott W.
May I ask a question?
Mark M.
Scott: sure, at this point, I'll try fielding from both of you in parallel as best I can
Scott W.
ok
View paste
If storage permission granted

storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
              LimeApp.getContext().getString(R.string.app_name));

storageDir => /storage/emulated/0/Pictures/[app_name]

Else
storageDir = LimeApp.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);

storageDir => /storage/emulated/0/Android/data/[package_name]/files/Pictures
this is how my directories are getting made. Ideally, I could save the images somewhere public without permission, but I don't think that's possible on Android 8
Mark M.
getExternalFilesDir() is public, though perhaps you and I are using different definitions of the term
Scott W.
I just saved a picture in that directory and am not able to find it or navigate to it in Device File Explorer
I'll try restarting the device
9:25 AM
Scott W.
by public I mean a photo app or file explorer app should be able to find the image and display it
Mark M.
you would need to get the file indexed by the MediaStore
on Android 9 and below, you can use MediaScannerConnection for this
Scott W.
yeah. I'm restarting to try forcing the indexing
Mark M.
your focus is on Android 8 -- is your app for some specific piece of hardware that only runs on Android 8? or is your app for more general use across Android OS versions?
Scott W.
general
Mark M.
then this gets to be an order of magnitude more complicated
I recommend that you read several things
First, the chapters on Scoped Storage in *Elements of Android Q* and *Elements of Android R*
and then the blog post series starting with https://commonsware.com/blog/2019/10/19/scoped-...
Scott W.
View paste
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            photoCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            final ContentValues imageDetails = new ContentValues();
            imageDetails.put(MediaStore.Images.Media.DISPLAY_NAME, imageFileName);
            imageDetails.put(MediaStore.Images.Media.RELATIVE_PATH,
              Environment.DIRECTORY_PICTURES + File.separator + context.getString(context.getApplicationInfo().labelRes));
            Uri imageUri = resolver.insert(photoCollection, imageDetails);
            ParcelFileDescriptor pfd;
            pfd = resolver.openFileDescriptor(Objects.requireNonNull(imageUri), "w", null);
            return new FileOutputStream(Objects.requireNonNull(pfd).getFileDescriptor());
        }
this seems to work well. No complaints from QA team for versions Q and above yet.
Mark M.
yup, that should be fine -- sorry, your focus on Android 8 suggested to me that you hadn't gotten this far on newer versions of Android
that code would more closely match Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) from your earlier code snipppets
9:30 AM
Mark M.
while at this point, few devices will getting new upgrades from Android 9 to 10+, it's not entirely out of the question, so all else being equal, I'd recommend having a consistent filesystem location
it may be that trying to avoid the permission request is sufficient to break past "all else being equal"
in that case, getExternalFilesDir() plus MediaScannerConnection.scanFile() should work
and, that's a wrap for today's chat
the next one is Saturday at 4pm US EDT
and as usual this chat's transcript will get posted to https://commonsware.com/office-hours/ shortyl
er, shortly
have a pleasant day!
Scott W.
Still processing. Thanks for the help!
Jan
has left the room
Scott W.
has left the room
Mark M.
turned off guest access

Tuesday, April 27

 

Office Hours

People in this transcript

  • Jan
  • Mark Murphy
  • Scott Wehby