I've inherited an Android kotlin app, and am scram...
# android
t
I've inherited an Android kotlin app, and am scrambling up the learning curve (done quite a bit of iOS). I need to make one simple GET request, not on the main thread of course. Is there a simple/idiomatic/not-too-bleeding-edge/lightweight/reliable go to for this kind of thing? I found okhttp and will probably use it unless there's a better suggestion. it looks pretty well supported, but it is originally a java library, and i wondered if there was something more kotlin-esque
j
Retrofit and kotlin coroutines should help
👍 1
👆 2
l
b
How about ktor? Asking for knowledge… I haven’t used it. I’m a retrofit guy.
l
Should be possible - though I can't say I've used it either
b
I believe ktor is more idiomatic for a kotlin app these days… it’s what’s recommended on kotlinlang.org anyway.
b
all the modern tutorials on android are in Kotlin. I highly recommend youtube 🙂
k
Retrofit and coroutines would be the best combination for you. You can just stick with retrofit to begin with as you can simply return
Call<T>
and handle the
error
or
success
case in that listener. Then once you are comfortable with coroutines, simply remove that
Call<T>
and mark you functions
suspend
. However, one caveat is that even though coroutines look simple, once you start digging into it there’s a lot of implementation details that went into it.
b
Is it just me, or is the retrofit documentation lacking a lot of information. (sorry for the blasphemy). I had to learn a lot from tutorials/ javadoc.
k
I can point you the best resource that is out their for retrofit. https://futurestud.io/tutorials/retrofit-getting-started-and-android-client . This literally covers everything.
b
Once using
suspend
, I would replace
Call
with
Response
though, as you want to check statusCode and stuff?
k
Yea i totally forgot what Retrofit callback it is I havent used it in a long time 😆
b
i was thinking of switching to okhttp directly. What are you using now?
k
You can use OkHttp as well. Retrofit is simply a layer on top of OkHttp and actually uses it underneath.
So if you use retrofit, you are using OkHttp.
b
yes thats why i said “directly” 😄
k
Totally depends on your need 👍 .
b
And what do you use?
k
retrofit. I’m aware of the internal workings of OkHttp as an HTTP client
b
ohh okay i thought when you said you forgot about callback, you mean you stopped using retrofit
k
No i should’ve been more clear, i forgot the callback because i use
suspend
functions on retrofit. my bad!
b
oh okay, but you don’t use
Response<T>
, just
T
?
k
yup. mark your function
suspend
and only provide what you want in return
b
This is what i mean by lack of documentation 😄
you dont know about
Response
for suspend functions
t
I've been off reading... I could see if I was doing a lot of http interactions, it would be handy to create a framework/dsl for declaring some of the boilerplate away and it looks like retrofit would be the thing for me then. Frankly, I was hope for something like: CurlLikeThing.asyncGet(url="a.b.c/foo/bar") { error?, data -> /* check the error and do something with the data" } Probably because in iOS, it looks like: URLSession.shared.dataTask(with: request) { (_data, _response, _error) -> Void in ... }
k
@Ben Butterworth oh i know what you mean. Its simply a wrapper and you can behave on either success or error
i check my response code instead.
b
How do you check it?
k
Copy code
fun fetchAuthenticationFlow(credentials: Credentials) = flow {
    val response = accountService.loginWithCredentials(credentials)

    if (response.isSuccessful) {
      val authentication = response.body() ?: throw NullPointerException()
      emit(authentication)
    } else throw IllegalAccessException("Unsuccessful response received.")
  }.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
j
My understanding is you execute the call and show the data of its not null and successful
b
So then you are using
Response
?
because thats the object youre using 😄
👍 1
k
@Ben Butterworth i read what i said above and i think i confused you asking if i use
T
in place of
Response<T>
. I assumed you were asking
Call
instead
b
ohh okay yea no problem. i still can’t find where i first read about
Response
k
Just to be clear you dont really have to use the
Response
wrapper though. https://proandroiddev.com/suspend-what-youre-doing-retrofit-has-now-coroutines-support-c65bd09ba067 you can simply return the object
You’d have to deal with the cases then, maybe in try/catch blocks not sure never did that. I like the Response wrapper class
b
Yea @Travis Griggs ive done that in iOS as well. Tbh, retrofit is way cleaner than having to write your own reusable classes for network requests? @KD, yes but then you wont have access to the response object, so its arguably less useful (cant check status code). I think using
Response
when using coroutines should be the default
j
Kotlin has a great wrapper class called Result https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
b
yes, i convert the
Response
into a result class, but i made my own
sealed class Result
because i didnt know about this. You still have to create it don’t you (retrofit doesn’t help here)?
k
I wouldn’t suggest the railway monad which
Result
or custom ones has to offer.
Its way better to deal with data layer exceptions at the data layer. With
Flow
you can react to your value and provide appropriate response to your views. This ways all the viewState logic gets nicely wrapped in the
Flow
or to put it in a nice manner, in one place. Then again, its my personal preference. One should always go with what they think is best for the project 😄 👍
j
Totally agree with you
b
Yea exceptions get treated in the data layer, but Result is passed to viewModels as they need to know the result 😄. The UI layer needs to know what happens too, so thats why I use
Result
. Isn’t using flow/ rx to pass data to Ui layer the same as using Result, just a different implementation.
k
You have to realize that introducing
Result
rail monad creates a lot of noise in your layers. You’d have to propagate this
Result
all the way to the presentation layer to your
viewModel
. That means your
domain
layer is now polluted with
Result
monad, and your business logic is now tightly coupled with this paradigm.
b
I don’t understand how flow/ rx can solve this?
k
You simply throw exceptions
b
and you have to handle the specific ones in the UI layer?
that doesn’t seem like an improvement?
j
But if I'm only returning a Result in my repository to be used in the viewmodel is that a problem?
k
It is if you are doing proper exception handling, which is a must regardless of you using
Result
monad or not.
@jefbit Not a problem. But your result wont be returned if an error occurred. Your viewmodel implementation has to deal with that case.
j
I still throw my exceptions in my repository layer. If the Result still fails in the viewmodel I post to a livedata value to display error in ap
👍 1
OK got it
b
if an error occurred. Your viewmodel implementation has to deal with that case.
But the UI might to display something based off this error. I guess if an error fails for you, you call a global function to warn the user about an error? Anyway, it looks like you’ve got the same pollution and tight coupling with flow. I don’t see any differences @KD?
k
Using Exceptions you can build as many hierarchies as you want. All you have to do is Inherit the base Exception class. Try doing that with
Result
monad where you have to add a new state.
If you have
Success
and
Failure
, adding a third state would need you to make changes everywhere where you are unwrapping the
Result
.
on the other hand,
catch
operator in the flow has
when
clause that can catch all the different exceptions thrown in its scope. No need to make changes in domain or data layer.
b
Yes, but that is the advantage of
Result
, you can add a third state. Of course, the Result can just have 2 subclasses (Success and Failure), which is what flow values you’re using is limited too as well? The
Failure
wraps the exception, so it behaves the same as what i think you mean when you say catching exceptions in flow. Im not familiar with flow, but I can have exhaustive
when
expressions and statements too, thats why I use a
sealed class Result
?
I’ll have to try Flow to see the differences
k
So if you are wrapping an
Exception
in
Failure
and your
Success
returns the good response, Why not elimiate
Result
monad, return good response as it is, and throw the exceptions for bad case.
What’s the point of wrapping
Exception
in another wrapper class. What are we achieving by it, when we get two choices in the end anways.
b
because we can add more subclasses to Result, the example the kotlin devs used is RecoverableError and NonRecoverableError
k
You can do the same with exceptions, without using
Result
monad. Throw
RecoverableException
and
NonRecoverableException
. Haven’t yet seen a case where
Result
monad can do a thing that using
exceptions
cant. And if i can achieve the same result without using a
Result
monad, then naturally i will go with that, no?
b
You’d have try catches inside your UI layer, as opposed to handling state the data layer gives you. Am i right that you’d have to remember this exotic hierarchy as it gets more complicated (the IDE is not as helpful)? Where as my
when
statement can be exhaustive/ safe
t
@Ben Butterworth "Tbh, retrofit is way cleaner than having to write your own reusable classes for network requests?" ... not sure I follow? is there a oneliner like that in retrofit that I don't see yet?
k
Well sure
when
clause will help you but if you’ve reached a point where your code depends on
when
clause suggestion by IDE then there are bigger issue in play. As far as try catches go, there’s only flow
catch
operator with
when
clause, and now that i think of it, what is stopping one from making a sealed class hierarchy of
exceptions
that your project uses. see,
result
monad has nothing that
exceptions
cant do.
b
@Travis Griggs Well ive just worked on an ios and android project, and the network code in iOS more verbose. The retrofit
interface
you write + some converters allows your
Service
to reduce the code in Android
👍 1
@KD, what you propose is double the amount of exception handling: first handle in the data layer, then throw a different set of exceptions that the UI/ VM layer understands? (or alternatively you propose leaking data layer exceptions to callers, which is even worse). It just sounds really messy both ways. Where as with the Result class, I convert exceptions into states the Result class represents, and its clear what the UI layers responsibility is:
switch/when
on that state. I’ll have to see some code to continue discussing this though.
👍 1
I will try flow and throw exceptions though, to get a better judgement on your idea
👍 1
k
Sounds good. Regarding propagating data/layer exceptions to presentation, its the same bridge with putting view state of
result
monad in the data layer. So this transitive dependency will always be there, and puts the conversation on a tangent.
t
OK I guess. I guess I'm not understanding. In iOS for a simple thing like this, I don't make new classes or anything. I just have one line to create a reqeust, and a second line to dispatch it and then the handler block takes care of things. for quick simple things, not complicated server interactions, this has worked well. should I aspire to something bigger and more complicated? It's a one time fetch of a firmware file that my app uses to update a BLE device...
I am impressed/tickled this question spawned such a lengthy thread 🙂
😃 2
👀 1
b
In that case you probably don’t need retrofit, you can just download the file with okhttp. https://square.github.io/okhttp/
Copy code
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
k
@Travis Griggs android official docs recommends using volley. https://developer.android.com/training/volley/simple . Compared to iOS, though the initial setup is a bit more, in the long run its more useful
b
Wow Volley is even simpler
k
I personally would go with
retrofit
though, given its an industry standard.
Yup easier. but then again in almost all implementations, Volley is combined with OkHttp as volley needs a HttpStack to create the RequestQueue. Basically separation of concerns is the key here.
t
Oh thanks! I'm off to check out that out now... bbiab
🙌 1
👌 1
I tried Volley, but it was causing issues that I don't understand with the legacy code. References to
R
were suddenly unresolvable. I dove into okhttp. Making a fungible shim which substitutes blocks in for the Response/Error classes brought me the quick one liner approach I was looking for
🙌 1
Thanks for all of the discussion, didn't understand all of it, BUT I understand more now than I did before 😄