Office Hours — Today, December 1

Tuesday, November 24

Mark M.
has entered the room
Mark M.
turned on guest access
Dec 1
8:25 AM
Kai H.
has entered the room
Mark M.
hello, Kai!
Kai H.
Hello
*bing*
Mark M.
how can I help you today?
I'm more of a DuckDuckGo person, myself :-)
Kai H.
By answering 1-10 questions
hail google
Kotlin: Are the following notations always interchangeable? fun get() {...} and fun get() = ...
Mark M.
you can always write fun get() = "whatever" as fun get() { return "whatever" }
er, sorry, fun get(): String { return "whatever" }
Kai H.
Ok
Mark M.
the reverse is not necessarily easy, if there are 2+ statements in the {...} part
Kai H.
And vice versa?
The .. = .. notation can only take one statement, righ?
Mark M.
right
8:30 AM
Mark M.
now, with scope functions like let() and apply(), a single Kotlin statement can get you farther than can a single Java statement
but, eventually, you hit a limit
and torturing your code to get it to use = syntax may result in stuff that is more difficult to read
Kai H.
I see where this is going ;-)
Mark M.
in the end, we usually want readable code
= syntax can help eliminate some syntactic overhead, but if you wind up with a mess on the right-hand side just to get it all shoved into a single statement, you might not have improved readability
Kai H.
Ok
Could you recommend a project with good Kotlin code to read (other than yours ;-) )?
8:35 AM
Kai H.
"Read this if you want to learn what a well written, well architectured Kotlin app looks like".
Mark M.
not really, insofar as I don't spend a lot of time reading through other project repositories -- I do that when I have a specific need to look at that project's code, not just as some sort of code critic
on the whole, newer architecture examples from Google are probably OK, with minor variances based on personal developer style preferences
Kai H.
It seems to be a bit... asleep
But thanks
When wanting a Dialog, do I _have_ to use DialogFragment if I want my dialog to survive orientation change etc.?
Mark M.
the code has been updated within the past year, which IMHO is a promising sign
8:40 AM
Mark M.
technically, a Dialog never survives a configuration change, if its hosting Activity is destroyed/recreated
what a DialogFragment does is automatically recreate the Dialog
if you want to manually recreate the Dialog, you are welcome to do so
but if you want the Dialog to appear after the activity destroy/recreate cycle, *something* has to arrange to show it again, whether DialogFragment or your own code
Kai H.
That answers this. As far as I remember, you seldomly use dialogs and don't create a "class hierarchy" around them, but just use them as they come from google, wherever you need them.
Mark M.
DialogFragment also provides a convenient fragment-y API for defining the content that goes into the Dialog that you can elect to use
there had been an anti-dialog push in mobile UI design for a while, but that seems to have died down somewhat
Kai H.
Do you mean the things you put into a Bundle?
By fragment-y
Drasko
has entered the room
8:45 AM
Mark M.
no, I mean onCreateView() and onViewCreated(), for describing the UI that appears in the dialog
Drasko
Hi, Mark! I had a really difficult time to find how to enter this chat. :)
Mark M.
your alternative is onCreateDialog(), which allows you to define the complete Dialog from scratch, but that's more code than you need in many cases
Kai H.
Ok
Mark M.
Drasko: hi! sorry, was the banner not showing up?
Kai: let me take a question from Drasko, and I'll be back with you in a bit
Drasko
Well, I went to https://commonsware.com/ and from there it was really difficult to find the way.'
Kai H.
Sure
Drasko
But, never mind.
Mark M.
Drasko: yeah, sorry, the Warescription site is the entry point for all of the Warescription services
Drasko
View paste
I am working on a launcher app project that I inherited. Now, I have to adjust the code to be compliance with Android R.
Mark M.
you will need to add a <queries> element to your manifest to be able to use the PackageManager APIs that you are using to find the launchable activities
Drasko
View paste
In my launcher I need to open programmatically file manager to show Downloads folder and I used:

val fileManagerDownloadIntent = Intent(Intent.ACTION_GET_CONTENT)
            val fileManagerDownloadUri = Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path)
            fileManagerDownloadIntent.setDataAndType(fileManagerDownloadUri, "text/csv")

but Environment.getExternalStoragePublicDirectory is deprecated in R. How can I get path to the Downloads folder?
Mark M.
you use the deprecated method
AFAIK, Google has not published an alternative mechanism
note that ACTION_GET_CONTENT does not take a Uri
Drasko
Yeah, I know that
8:50 AM
Drasko
I used it and just set type to fileManagerDonwload, fileManagerDownloadIntent.type="text/csv"
But it doesn't point me to the Downloads folder.
File manager points me to Recent.
Mark M.
again, ACTION_GET_CONTENT does not take a Uri that way
Drasko
Hm, how can I open Downloads folder in pre-R Androids?
Mark M.
ACTION_GET_CONTENT has *never* taken a Uri
so, if your approach works on some older devices, consider that to be a minor miracle
Drasko
To be honest this is the code I inherited, so ... :)
Mark M.
ah
I am not aware of any reliable means to get a system-supplied "file picker"-style UI to open on a particular directory
for a few more months, you could embed your own file picker UI (hand-coded or from various libraries)
there are probably some unreliable means, such as ACTION_PICK (which *does* take a Uri)
8:55 AM
Mark M.
and there are ways that you could arrange to show the system-supplied file picker starting at some previous directory that the user had used with that file picker
8:55 AM
Kai H.
I would like that functionality too. When using SAF, it takes you to the last directory that was picked, with not means to pre-choose one.
Mark M.
if you save the Uri from a previous SAF operation, you should be able to include it using EXTRA_INITIAL_URI on a future SAF operation, to use as a starting point
Kai H.
But I don't have a previous SAF operation. I just want the user to pick a certain folder.
Mark M.
sorry, the user gets more freedom than that
Kai H.
Rather a certain collection, "Documents" to be precise. But it's not implemented.
"collection".
Drasko
I just tried previous code on some non-R emulator and it does open Downloads folder.
Mark M.
there are 26,000+ Android device models out there
do not assume that they all work like that emulator
even when you stick within the bounds of documented behavior, we get weird stuff from time to time
if you go outside the bounds of documented behavior, "all bets are off"
in particular, prior to Android 5.0, ACTION_GET_CONTENT was purely an app-level Intent action -- the system did not provide anything for it
(though it's reasonably likely that your minSdkVersion is 21 or higher by this point)
9:00 AM
Mark M.
Drasko: let me take another question from Kai, and I will return to you in a bit
Kai: do you have another question?
Kai H.
When doing "rebuld project" in Android Studio and then "run", it builds something again. Why is that? What does it build in the respective steops?
Mark M.
off the top of my head, I couldn't tell you
but the Build tool in Studio should tell you
Drasko
Rebuild project cleans the project and build.
Jan
has entered the room
Jan
Hi.
Kai H.
Where do I find the build tool?
Mark M.
(hi, Jan -- I will be with you in a moment!)
9:05 AM
Mark M.
Kai: after a build, it is docked by default in the bottom toolstrip at the bottom of the IDE
Kai H.
Oh, that, ok.
Mark M.
right now, in my Studio, it is showing up next to Logcat
Kai H.
:D
Mark M.
that lists all the Gradle tasks that went into the last build operation
those that lack "UP-TO-DATE" actually did something
(OK, also excluding those with the "not" symbol instead of a checkmark, as I assume those also get skipped)
even the "UP-TO-DATE" ones technically do a bit of something, in that need to examine files and confirm whether they are all up to date
which is why you may see more than 1 ms times for some of those
let me take a question from Jan, and I'll be back with you shortly
Jan: your turn! how can I help you today?
Jan: do you have a question?
9:10 AM
Mark M.
Jan: if you come up with a question, just let me know
in the meantime...
Drasko: back to you! do you have another question?
Jan
View paste
Hi, Mark. My question is about cast warnings.  In my hunt for common error handling, I have this sealed class: 
sealed class APIResult<out T : Any> {
    data class Success<out T : Any>(val data: T) : APIResult<T>()
    data class Error(val errorMessage: String) : APIResult<Nothing>()
}
 that I use on retrofit returns.  But I have to cast every return in the when block and get a cast warning: 
    val contentData : LoginUser = result.data as LoginUser
Any better way to handle it?
Mark M.
if you do not have as LoginUser, what happens?
IOW, if you have just val contentData: LoginUser = result.data, what is the error message?
Jan
I haven't tried that.
Mark M.
if your result is an APIResult<LoginUser>, it should "just work" to assign result.data to a LoginUser property or variable
Drasko
One more thing regarding deprecation in R.
Mark M.
Jan: but, this is one of those things that I usually need to poke around in the IDE a bit, reading error messages, until I get it worked out
Drasko: go ahead!
Drasko
I receive warning message: 'getter for systemWindowInsetTop: Int' is deprecated.
9:15 AM
Mark M.
yeah, they changed the system insets APIs around -- I forget the details, as I have generally avoided those APIs in the first place :-)
Jan
I tried it. Now a new warning that inferred type is Any but expected LoginUser. The result is APIResult<Any> because I use this sealed class for all returns and the return can be lots of different objects. I'm not that good with generics so thought maybe there was a better way to handle all the casting. I can suppress the warning or just not worry about it.
Drasko
But I don't know how to get appropriate call on that one. This is used to set padding/margins so the views are not drawn behind status and navigation bar.
View paste
For example:

binder.root.setOnApplyWindowInsetsListener { _, insets ->
            // Add padding to bottom sheet
            val top = insets.systemWindowInsetTop
            val bottom = insets.systemWindowInsetBottom
            bottomSheet.setPadding(0, top, 0, if (bottom != 0) bottom else 30)
//..
Mark M.
Jan: while you might use APIResult in lots of places, any *given* APIResult should be tied to a concrete class
Drasko: according to the docs, use getInsets() insetad of systemWindowInset*
Kai H.
Drasko
Thanks, Kai.
Mark M.
Google's Chris Banes also has written quite a bit about window insets
9:20 AM
Mark M.
at this point, if anyone has further questions, just ask, and I'll field them as best I can in the time remaining!
Kai H.
I get a directory from the user via SAF and then do some operations in that. Is there any way to test that?
Drasko
Thanks, Mark, I'm good.
Mark M.
Kai: test what, specifically?
Drasko: OK!
Kai H.
Getting a directory via SAF and then writing to and reading from it.
Jan
View paste
The problem with that is my common processing function:     fun processResponse(response: Response<Any>) : APIResult<Any> {
where I check the retrofit response and make it success or error inside APIResult. So even if I knew how to pass in my class type of LoginUser for conversion, it'd sometimes not return that but Error object instead.
Kai H.
But one could argue that should just work and needs no testing. As with most file system functionality X)
Mark M.
Jan: processResponse() may need to use Response<*> or Response<T> and APIResult<*> or APIResult<T> to retain your types
9:25 AM
Mark M.
Kai: I would isolate the logic that does the I/O from the logic that operates on the data being read or written, then test the latter
Jan
What does * do? I thought T was already for all types? And if I make that change, do I need to pass in the type for T somehow?
Mark M.
Jan: * says "for here, we don't care about the type" -- T says "for here, we care about the type, but the type is determined by the call site"
Kai H.
The "logic" is for example get a directory and a subdirectory via DocumentFile, checking if there is write access, then writing a file and reading it again.
Mark M.
Jan: if you want to use T, T will need to be visible to processResponse(), either because that is a member function of some class that defines T, or by declaring T in the function (fun <T> processResponse(response: Response<T>): APIResult<T> { ... })
Kai: you could test that with instrumented tests, if you extract the "create the DocumentFile" logic into different implementations for the main code (uses SAF Uri) and the test code (uses a File)
*maybe* DocumentFile survives in unit tests, perhaps with the help of Roboelectric, but I haven't tried that
Kai H.
:)
9:30 AM
Mark M.
alternatively, hide all the stream API in something that has different main vs. test implementations, and test the stuff that uses the streams
Kai H.
I would like to test the thing that does the streaming though, it seems :D
Or prepares for that up unto openInputStream etc.
Mark M.
then, either take advantage of DocumentFile working with Uri or File values, or you would need to cobble together something with UiAutomator that actually automates the SAF UI itself
and that's a wrap for today's chat
the next one is Thursday at 7:30pm US Eastern
Kai H.
Thanks for your answers :D
Mark M.
I am sorry if I did not get to all of your questions
and, have a pleasant day!
Kai H.
Same
Kai H.
has left the room
Drasko
has left the room
Jan
has left the room
Mark M.
turned off guest access

Tuesday, November 24

 

Office Hours

People in this transcript

  • Drasko
  • Jan
  • Kai H.
  • Mark Murphy