Mark M. | has entered the room |
Mark M. | turned on guest access |
Nov 7 | 8:30 AM |
Urban | has entered the room |
Mark M. |
hello, Urban!
|
Urban |
Hi Mark. Nice to see you again!
|
Mark M. |
how can I help you today?
|
Urban |
I have a few questions for you once again! Let's start!
|
Urban |
First question is quite basic: use of setRetainInstance(true). I have recently seen some posts suggestings one should use sRI(t) only for Thread safety and where extensive data have been fatched beforehand. However, I have been using it sometimes only to deal with config changes. What's your oppinion on that one?
|
Mark M. |
well, mostly, I have been using ViewModel recently, which under the covers uses retained fragments to retain the ViewModel instances across configuration changes
|
Mark M. |
beyond that, there is no "bright line" dividing what you want ("sometimes only to deal with config changes") and the advice that you read
|
Urban |
But is this generally a good measure to use to deal with config changes?
|
Urban |
I thought so but then I read it's not reccomended
|
Nov 7 | 8:35 AM |
Mark M. |
well, if the fragment has no data, there is no need to retain the fragment
|
Mark M. |
or, if the fragment gets its data from something else that is retained (such as a ViewModel), there is no need to retain the fragment
|
Mark M. |
if the fragment has data, your choices are the saved instance state Bundle or retained fragments
|
Urban |
SO, basically, you would encourage me to use it with extensive data but not when data is simple/not existant?
|
Mark M. |
IMHO, there is no value in retaining the fragment if there is no data
|
Urban |
I see. Thank you very much for answering that!
|
Mark M. |
where the dividing line is between "simple" and "extensive" is unclear
|
Urban |
Second question is also quite general: use of Activities (A) vs Fragments (F). In my projects, I have been extensively using A as building blocks but recently more and more focus is on using F as building blocks. That being said, would you suggest me to use them more in the future and, if so, to rewrite my projects to use Fragments instead?
|
Mark M. |
Google is steering developers towards fewer activities and more fragments
|
Mark M. |
where many simpler apps might be implemented as single-activity apps
|
Mark M. |
so far, particularly when using the AndroidX fragments, I have not run into problems with Google's vision, and it simplifies some things
|
Urban |
Simplifies what specifically? retaining data?
|
Mark M. |
sharing data and events between screens, such as via shared ViewModels
|
Urban |
You probably refer to using single VM for many Fragments, correct?
|
Nov 7 | 8:40 AM |
Mark M. |
I prefer "shared" to "single", but otherwise, yes
|
Urban |
So in general, you would encourage me to use more Fragments in next projects?
|
Mark M. |
unless you have some clear reason not to, going in Google's direction is usually easier than trying to "fight the current", as it were
|
Urban |
That being said, should I also rewrite my current projects from A to F or would this be an overkill?
|
Mark M. |
I cannot answer that. I would not change the implementation "just because", but if you need to change the architecture for other reasons, switching to fragments might be part of that change.
|
Urban |
Thank you for your thoughts on this one!
|
Urban |
Third question is abour Coroutines in Kotlin. More specifically, about "suspend" functions. In general, I do understand that this function enables concurrency. However, I have recently come accross that "suspend" is mandatory if you wish for a function to run on back T whereas I thought that this is solely influenced by use of appropriate Dispatchers on Coroutine. Therefore, should I just mark every function as "suspend" to be safe or is that claim not correct?
|
Mark M. |
suspend has no direct impact on threads
|
Mark M. |
suspend says "feel free to suspend execution of this code at this point and pick it up again later"
|
Mark M. |
and, from a practical standpoint, you add suspend to a function when the compiler yells at you to add suspend to the function :-)
|
Mark M. |
some libraries may use suspend as an indicator that a certain threading model should be used -- Room and Retrofit do this
|
Nov 7 | 8:45 AM |
Urban |
In my case, I was reffering to Retrofit return type yes
|
Mark M. |
if you put suspend on a Retrofit API function or a Room DAO function, then the code generation will arrange to do the network or database I/O on background threads using coroutines
|
Urban |
Ahhh I see. So basically "suspend" only gives a real threading impact for the models you mentioned but not in general
|
Urban |
I was confused a little bit on that part as I thought I new suspend has no impact on threading
|
Urban |
So, as you suggested, one should only use "suspend" where absoutely neccessarry, correct? Do you have any cases in mind?
|
Mark M. |
well, for example, if you have a Retrofit API, and you are wrapping that in a repository, many of your repository functions will need the suspend keyword, because they are calling functions on the Retrofit API that you marked with suspend
|
Mark M. |
any function that calls a suspend function needs to either be a suspend function itself or wrap the suspend function call in a coroutine builder (e.g., launch())
|
Mark M. |
that's what I meant by the compiler yelling at you -- if you call a suspend function from a regular function, the compiler will complain
|
Urban |
That being said, if you are not using Retrofit nor Room there is no real value adding "suspend" to functions called from coroutine correct?
|
Mark M. |
AFAIK, that is correct
|
Urban |
Thank you very much for answering this one!
|
Nov 7 | 8:50 AM |
Urban |
Next question is about testing in general. I have recently looked more deeply into testing and found out that this is a very broad area where many "basic" tests have to be performed for "banal" purposes (such as zero element check in a new listView,...). Therefore, I can imagine if one has to do all the unit tests for every scenario, a lot of time has to be dedicated for this. That being said, how would you reccomend someone to do the testing. Everything, some specifics or avoid it (as I ddid in the past)?
|
Mark M. |
I definitely would not avoid it
|
Mark M. |
I do tend to avoid Espresso tests, as I find them to be hopelessly flaky
|
Mark M. |
so I focus testing on viewmodels and down (repositories, data sources, etc.)
|
Mark M. |
and I try to push as much logic into those testable layers as possible
|
Mark M. |
with a goal of having activities and fragments that are "as dumb as a box of rocks"
|
Mark M. |
so there is very little that can go wrong directly in that mostly-untested bit of code
|
Urban |
Do you there focus specifically on mocking responses which you can only get very rarely or you try to test all scenarios there?
|
Mark M. |
that depends on how you define "all", I suppose
|
Mark M. |
I try to test both positive and negative scenarios, such as the server returning the actual content with 200 OK and the server returning an error
|
Mark M. |
I only try to test multiple error scenarios if the client-side code cares about those scenarios (e.g., does a login thing for a 401 Unauthorized response and does something else for other errors)
|
Urban |
So you do not test the simple scenarios of every particular class but more on the interaction of components, correct?
|
Mark M. |
I do not really think of it in those terms
|
Nov 7 | 8:55 AM |
Mark M. |
if I have code coverage tools working, I aim for strong coverage of the tested components, though perhaps not 100%
|
Urban |
Aha okay, I must have missunderstood you at the beggingg
|
Urban |
begining*
|
Urban |
Which tools do you mostly use for this (apart from JUnit)? Mockito, Roboelectric or any else?
|
Mark M. |
I'm a Mockito fan, particularly using the nhaarman mockito-kotlin library
|
Urban |
You mentioned " so I focus testing on viewmodels and down (repositories, data sources, etc.)". You probably meant that you mock responses from repositories/data sources/... and then you see how are they used to populate your UI?
|
Urban |
to populate your VM and from there specific component of UI*
|
Mark M. |
for example, when I test a viewmodel, I confirm that all of the viewstates and events emitted by the viewmodel make sense, given the inputs that I have provided in the test
|
Mark M. |
in production, the activity or fragment observe those viewstates and events and take actions -- I do not worry about testing that stuff, as the testing is very complicated for limited value IMHO
|
Nov 7 | 9:00 AM |
Mark M. |
to make that work, though, I try to keep the activities and fragments as lean as possible, so that there is very little real business logic in them
|
Urban |
I see. So instead you focus mainly on testing state of your VM, which basically reflects state of UI (if done correctly).
|
Mark M. |
yes!
|
Urban |
thank you for these - I definetely need to look more in testing which should provide some benefits
|
Urban |
We are slowly coming to the end of my questions
|
Urban |
View paste
|
Urban |
(Sorry for paste, dont know what I have done there)
|
Mark M. |
yeah, this chat gets a bit strange with pasted information
|
Mark M. |
personally, I am not a fan of two-way data binding; I prefer unidirectional data flows
|
Mark M. |
and, in my defense, Google seems to be heading that way too with Jetpack Compose
|
Mark M. |
that being said, what I like and what you like do not have to be the same :-)
|
Mark M. |
and Google certainly supports the two-way concept, even baking it into their data binding framework
|
Urban |
Are there any clear benefits having this complicated layer for the immutability?
|
Urban |
One possible seems that if two things take one objects which, if immutable, will always be the same
|
Mark M. |
I can't answer that, as I don't implement UI code that way
|
Mark M. |
my general point is that if you are comfortable with your approach, feel free to use it
|
Urban |
Okay, thank you for your answer
|
Nov 7 | 9:05 AM |
Urban |
But probably I need to be more careful when dealing with changed objects, correct?
|
Mark M. |
um, probably, but it's a little difficult to say given just the descriptions here in the chat
|
Urban |
I see. Thank you very much for your opinion on that one.
|
Urban |
Now, my last question *hurray*
|
Urban |
Last question is about Koin. More specifically, I am interested where should one use "factory" instead of "single" when creating a Koin module. I understant their destinction but cannot see where specifically should a new instance (factory) be genereated every single time (you used it for DataSource instance which fetched POJOs from the server side using Retrofit).
|
Mark M. |
I have not had a need to use factory yet
|
Urban |
I believe you used it in Kotlin Coroutines book
|
Urban |
Page 120 of last version
|
Mark M. |
hmmm... you're right
|
Mark M. |
I forget off the top of my head why I chose factory there
|
Urban |
No problem!
|
Urban |
I was just interested why you used it (as you did not provide explantion for it)
|
Mark M. |
in some cases, it may be due to changes in the environment
|
Mark M. |
for example, there I use factory for ObservationRemoteDataSource
|
Mark M. |
suppose that the Uri that we needed to use changed based on user input
|
Mark M. |
so we hit different servers based on different user input
|
Mark M. |
in that case, the remote data source might need a new instance each time, so the instance that we get from Koin is set up for the now-current base Uri
|
Urban |
I see! So basically, when we have one non-changable class we use "single" whereas if that instance depends on some user input we use "factory". Am I correct on that one?
|
Nov 7 | 9:10 AM |
Mark M. |
I would phrase it more as "whereas if that instance depends on some external factors we might use "factory""
|
Mark M. |
it would not necessarily need to be user input -- it might be based off of configuration data supplied by a server or something
|
Mark M. |
to be honest, I really do not know why I used factory in this case, though
|
Urban |
But probably where the instance is tightly connected by these factors, right?
|
Urban |
I was not sure either
|
Mark M. |
sorry, I did not follow your "tightly connected" comment
|
Mark M. |
in terms of this sample app, the only real input into ObservationRemoteDataSource is the OkHttpClient, which is not changing during runtime, so single should work
|
Urban |
Err ignore it, I understand whay you tried to say before
|
Mark M. |
I have made a note to try to figure out why I used factory here and either replace it with single or explain it more in the book
|
Urban |
Okay, amazing! Glad that I could be of help
|
Urban |
that would be everything for now. Thank you again Mark for your help!
|
Mark M. |
you are very welcome!
|
Urban |
And I will see you again soon enough!
|
Urban |
Have a nice day :-)
|
Mark M. |
you too!
|
Nov 7 | 9:30 AM |
Urban | has left the room |
Mark M. | turned off guest access |