Untangling Coroutine Testing by Márton Braun

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
foreign [Music] hi everyone it's great to see the interest in testing my name is Martin Brown I'm a developer relations engineer on the Android team and today we'll untangle curving testing together as you might know testing core routines is not obvious we have to be able to call suspending functions we have to think about multi-threaded parallel code running during the tests we have to wait for the completion of asynchronous things and we have to manage all of that complexity somehow so that's what we'll cover today luckily we have a testing library for this from judgments which is cotton X curtains test so we'll be using those apis during this talk a quick overview of what we'll cover we'll start from the very basics of just calling into suspending functions in tests then we'll take a look at the theory and the overview of the current investing API we'll take a look at some best practices some entry specific things like handling the main dispatcher and finally we'll wrap up by taking a look at flows and state flows so uh let's get started done with the basics of calling suspending functions in tests here's a super simple suspending function to start with it delays for a second and then it Returns the hello world string if I want to write a test which asserts the return value of this function it might look like this it calls the function and then we assert whatever it returned this of course doesn't work on its own because we're only allowed to call suspending functions from suspending functions so we need to get inside according to be able to call this function during our test and this is what quarantine Builders serve Bridging the Gap of regular blocking code with the world of the suspending with the suspending world of coroutines and we have a special carotene builder in the testing Library called run test that we can use here and by wrapping our code with ROM test we can already run this test and it would be successful a couple things to point out with this simple example first of all ROM test will use a test dispatcher implementation under the hood which we'll take a look at in a bit but importantly it will skip that delay that's in the code that we are testing so instead of our test taking a bit more than a second it will complete almost immediately and you can also see the convention I'm using here to call run test with an expression body just returning the result of the run test call directly from my test function this is a nice convention to use this library and if you happen to be targeting JavaScript with your tests it's actually required as well so that's for a start what if we start complicating our creatine code a little bit one complication we can introduce is we can switch dispatchers within the code that we're testing so for example in the fetch data function I can use with context to go to the i o dispatcher and do the work of that function there if we run the code again our test will still compile and run and pass however we now have some problems in it namely it will now take an actual second to run the test because the delay is now handled by the i o dispatcher to visualize this we'll use these visuals where we have two threads represented one of them the main test thread that we're using and the other thread from the i o dispatcher so as we start our test we call fetch data which happens on the test thread we go into the body of the fetch data function where a with context call happens taking us from the test thread into the i o dispatcher where we perform that real one second delay and as we exit that function and with context we get back our string and perform an assertion which is successful because we're still returning the correct value we just have the delay that we really don't want to be a real delay in a test some other things that can complicate your testing is if you are creating new core routines in your test outside of the main task routine that run test creates for you so here's a direct example of that where we have a test that creates a user repository object and then it uses two new Corinthians to register two users in that repository and finally we want to assert that the users have indeed been added to the repo here it's obvious that we're creating new curtains because we have these launch calls right there in the body of the test function we can also make this less obvious here's a super simplified version of a view model which stores a user and has an initialize function which can launch a new creatine using the jetpack view model scope API and then that creates a new curtain to make that fetch happen uh if we're testing this we might do something like this we can create an instance of the view model we call that initialize function to perform the data loading and then we might want to assert that the username that the user object has been set and that they have the correct name for example um here even though we are not creating curtains directly in the test body we are creating coroutines in the code that we're calling into from the test so we are still creating new curtains during the test as it is right now both of these tests would fail for various reasons and we'll examine that in just a moment however before we do that we need to learn about the available coding testing apis in the test Library so all of this starts with run test which we've already seen it's a special coroutine Builder which lets us create curtains for testing purposes because all core routines need to run in a keratin scope run test has to use according scope ends as well and it uses a test scope to run that test 13. what Tesco does is it will always use a test dispatcher inside it and and this is the last concept that I have to introduce I promise that will depend on a test creating scheduler which is really the core of the testing library and where most of the implementation actually happens so um while run test creates a test scope and that scope creates a dispatcher a test dispatcher automatically you can also create additional test dispatchers during your tests as needed but something extremely important with this library is that all of these test dispatchers must always share the same scheduler this is so important that I wrote it out on the slide so make sure that as you're instantiating new dispatchers you only have a single scheduler instance within the entire test and we're going to refer to that later on so scheduler is where most of the implementation lives and we have the test dispatchers which are currently in dispatchers that depend on this scheduler and test dispatcher is actually an interface with two different implementations available in the library standard test dispatcher and unconfined test dispatcher both of these rely on the scheduler behind the scenes they just have slightly different Behavior standard test dispatcher will queue up any core routines that you start on it it will queue them up on the scheduler and they will only execute when they get time to execute or you advance them manually and on confined test dispatcher will start and start executing a newly started curtains eagerly so we'll look at both of these in detail to understand their behavior starting with standard test dispatcher so as I said this queues of proteins using that scheduler behind the scenes you need to either yield the test thread to them or you can advance them manually and this is what run test will use by default or more specifically when you call run test it creates a test scope and a test scope will create a standard test dispatcher by default if you don't specify anything else so let's go back to this test where we saw that we create a repository we launched those two new Corinthians to register Alice and Bob in the two hurricanes and finally we check that the list of users in the repository after these calls is contains Alice and Bob let's see how this test executes and these launches within the test body are being called on the receiver of the Lambda that we're passing into run test which is the test scope itself so we are launching this in the test scope and inheriting the standard test dispatcher that's part of the scope by default so let's execute this we start from the test thread of course we create a user repository this happens on the test thread then we launch a new carotene which again Launches on a standard test dispatcher which means that we don't start running it yet instead we just place this on the scheduler to be executed later and launch will return immediately without that new core team actually running you might notice that this is very similar to what happens in production code with real dispatchers where it launches this fire and forget call where launch itself returns immediately and then decorating that it created will get dispatched when the dispatcher can handle it so we keep running our code here we go to the next launch again this will create a new core routine put it on the scheduler and return immediately and that gets us to our assertion which at this point will fail because we never actually registered those users so we just created coroutines that would have registered them so how do we fix this we can advance core routines that are pending on the scheduler manually we'll take this example of a scheduler state which has a bunch of different coroutines waiting and some of them are due right now with no time constraints and some others are scheduled to be executed after some delays at some future time uh the first function we have to be able to advance these is run current this is a fairly self-explanatory name it will start taking the core routines that are on the scheduler which are due at the current time and execute those then we have advanced Time by which allows you to fast forward the virtual time that the scheduler is using internally by some amount of milliseconds for example let's say that we Fast Forward by 100 milliseconds and it will advance any of the coroutines it will execute any of the coroutines that were scheduled before that time importantly if you had something pending at exactly the time where you just fast forward it to it won't be executed yet and finally we have a catch-all solution which is Advanced sample idle this will just keep taking things off of the scheduler and executing them as long as there's something to do so this would clear our scheduler entirely executing everything on it we could really use any of these to fix the problem we had in our test we're going to do the simplest thing which is just called Advanced until idle if we now run the test again we'll see that this will fix it so we'll first start again by creating the repo then we'll perform those two launches which Q of two core routines on the scheduler and as we get into advanced something idle that will start picking up tasks from the scheduler and executing them so that means that we would first register Alice then we would register Bob and now that the scheduler is Idle advance until idle would return we would get to our assertion and it would be successful because we've now actually registered the users before asserting on it okay that was standard test dispatcher we'll now look at what unconfined test dispatcher does in contrast to that again this will start new curtains eagerly instead of queuing them up this might be a good choice for simple tests however be careful with it because it really doesn't emulate real concurrency so if the concurrent behavior of the code that you're calling is an important part of your test at all you should really be looking at standard test dispatcher to start with and only use unconfined as dispatcher in certain scenarios some of which we'll look at here we can test this we can try out this dispatcher in the existing test that we were working with and to make runtest use it we can just create an instance of it and pass it in as a parameter to run test this way it will be placed in the test scope that runtas creates instead of creating a new test dispatcher in there and our two launch calls will now inherit that dispatcher from the scope so these launches will now happen on an unconfined test dispatcher let's see how this would execute so first we create the repository again then we get to our first launch call which because it launches on an unconfined test dispatcher is entered eagerly so we immediately register Alice within the call to launch and only then does launch return then we go to the next line we launch a new creatine we enter it eagerly we perform that registration and then launch returns and we get to the assertion which of course is successful now so to summarize these two dispatchers standard has a queuing behavior unconfined has eager start for curtains and you should use standard by default but there are some use cases for unconfined as well such as using it for the main dispatcher and for using it Encore routines that collect flows which can also make your life a bit easier okay uh with that let's start looking at some best practices around creatine testing and the main point I want to drive home here is injecting dispatchers which is very important for this example we'll use this made up repository class which depends on a database and it uses the i o dispatcher for a couple different things first it creates its own internal correcting scope where it uses the i o dispatcher and it has an initialize method that launches a new creatine in that scope on the i o dispatcher and populates the database with some kind of initial data then it also has a different kind of usage for the i o dispatcher which again is hard-coded here which is a suspending function that switches the caller to the i o dispatcher and then fetches data from the database in there if we want to test this repository we might write a test like this here we create an instance of it pass in some fake database implementation and then we call initialize to set it up with some data after that we want to grab the data to assert on it so we call fetch data and we make an assertion let's see what happens if we run this code we'll have the test thread yet again and now a couple threads from the i o dispatcher represented so we start as always on the test thread we create our object then we call initialize which remember launches a new core routine on the i o dispatcher that's denoted here with the dotted line so that's a Second Corinthian and in addition to the one that runtest has created for us it starts populating the database with data however this launch will return immediately because we are just using a production dispatcher so while that's happening our main character continues and we call fetch data which again takes our main protein to the i o dispatcher and performs a read of the database there after that when the read is done fetch data returns and we move on to the last line of our test which is asserting and this might be successful if we were lucky enough that that populate core was scheduled quickly enough and completed quickly enough that by the time that we've read the database on potentially another thread the data was already there or if we're unlucky populate was scheduled too late took too long and our assertion fails if we're really unlucky populate can just keep going even outside of the scope of the test because there's nothing that ties it to the test itself and cause side effects during our other test execution for example so this is really not ideal so let's see how injecting dispatchers can help us so instead of using the io dispatcher directly in a hard-coded way we can add a parameter to our repository which is a core routine dispatcher and then use that wherever we were previously using the i o dispatcher directly we could also make the default value so that it doesn't have to be provided in production code every time a quick side note here you have other options than injecting a quarantine dispatcher if you want you can also inject the broader correcting context type that lets you do things like specify other elements of the correcting context for testing purposes pass in an empty correcting context if you want to and if you only need to start new core routines in your test but you don't need the context switching part of it you can also consider injecting a core routine scope it's the same idea between behind all of these different options in this talk we'll cover dispatchers but you can check our testing guidance for the rest so we're going to stick with this patcher for now uh let's see how this changes our test we now have to provide two parameters to the repository in addition to the database we want to pass in a quarity in dispatcher so I'll create a new standard test inspection object for this and I'll make sure again remind remember always share the one test scheduler that exists within your creatine tests so I'll grab the test scheduler property of the test scope that we're working with and pass that in to the newly created standard test dispatcher let's see how the code executes now with this change we create the Repository we call initialize which Launches on the test dispatcher now which we injected so that will queue up a new car routine on the scheduler because we're on a standard test dispatcher then initialize returns and I've already added an advanced until idle call here because I knew that this was going to happen so that will pick up the weighting coroutine and execute it so by the time that we go to the next line where we used to go to the i o dispatcher we now switch context to the standard test dispatcher so we just remain on the same thread again and perform the database read there and because everything was nice and sequential now we can perform our assertion without having to worry about race conditions there is an important note here which is that this shouldn't be happening in a test so let's um recognize what the repository class is doing it has this initialize method which internally launches a new core routine to load data asynchronously and the colors of the repository have to know when that asynchronous work completes inside the repository because it's other methods like fetch data are only safe to call after the data has been loaded but the repository doesn't give any indication to the outside world of when this asynchronous work will complete so we need to work around it so injecting a dispatcher here and using the test apis to advance the pending core routines is something that can be done in tests but you wouldn't be able to do this in production code in production code you would be guessing whether or not this asynchronous work has completed which is really not ideal so if this work has to be asynchronous you can consider doing something like this using the async Builder and returning the Deferred object which is obvious that it can be awaited at the call site and then that works in tests and in production code or you might just review this and realize that the initialize method does not have to work asynchronously and it might just be that it could be a simple suspending function like this all right let's touch on something Android specific which is handling the main dispatcher um usually when you use the main dispatcher there are cases where you can inject it the exact same way as we've just seen by adding it as a Constructor parameter to whatever classes need to call into the main dispatcher but there are apis that don't allow you to do that for example here's a view model which uses the jetpack view model scope API it has a state flow where it stores message and it has a method to load that message by creating a new core routine and here I'm just directly writing a value into the state flow but pretend that that core routine is doing some kind of useful loading work so uh here vmodel scope doesn't allow us to inject a test dispatcher for testing purposes it has a hard-coded main dispatcher internally so if we try to run this test which creates a view model calls load message and then wants to assert that the State closed value has been updated this test would actually crash when we try to access the main dispatcher in it because we don't have access to the real Android UI thread in a unit test and we cannot inject test dispatcher in its place either luckily for us the exception message does point us to a possible solution which is that we can use dispatcher's set main from the testing Library so let's see how that's done set main takes a test dispatcher instance as a parameter so I can create test dispatcher here again making sure that I share that scheduler from the test scope and I can pass that in to set main then there's also a corresponding teardown method which is dispatcher's reset main so that this test dispatcher that we've statically set up to be the main dispatcher whenever it's referenced can be cleared at the end of the test so I can for example surround all of my test code with a try finally and then at the end of that make sure that I reset the main dispatcher so running this code this assertion and this test would now be successful when inside load message we launch a new creatine on The View model scope that launch will actually happen on the test dispatcher that we've set up to be the main dispatcher something interesting to note here is that I've used an unconfined test dispatcher this time to be the stand then to be to be the mocked version of the main dispatcher and the reason for this is that I wanted it to launch core routines eagerly uh this is because view motoscope also has a similar Behavior internally it uses dispatcher's main immediate which has a behavior that if you call the load message method from the main thread then launching something on the immediate main dispatcher will enter that quarantine eagerly without having to wait for an extra dispatch so by using on confined test dispatcher here I have the same scheduling behavior in my test as I would have in production code when I call the load message function from the UI thread this pattern of set main reset Main and every one of our creatine tests that needs to reference main dispatcher is not convenient so we can of course extract this for example sticking with junit 4 here but you can find Alternatives in all of the other testing Frameworks as well I can create a test rule which sets the main dispatcher at the start of each test and then resets it after each test and as you can see I'm creating an uncompined test dispatcher here as the default value of that dispatcher to use this rule I would have to create an instance of it and place it into the task class I have as a property and annotate it with the rule annotation and since this will handle setting and resetting the main dispatcher for me my test code can now focus on what I actually want to test which is creating the view model and then calling various things on it an interesting question at this point is whether or not I've managed to do scheduler sharing properly again there must only be one scheduler in your tests and we know that runtest will create a scheduler and we've seen that we created this Patcher inside the main dispatcher rule so that also probably creates a scheduler the good news is that the testing library has this very convenient Behavior where if you place a test dispatcher into the main dispatcher by calling sat main then any test dispatcher created after that point in time will look at Main first and if there's a test dispatcher there already grab it scheduler and share it so that means that before that because we are setting main before the test run test will automatically share that scheduler in the background and if I were to create additional test dispatchers during my test that would also be safe to do because main has been set and whenever these test dispatchers are being constructed they will look at Main and grab the scheduler from there okay uh so that was a bunch of things around regular proteins suspending functions we learned about injecting dispatchers and now I want to move on to looking at flows for our first example of flows I will have this example where we have a data source that's producing a flow of ins and we'll have a repository that depends on this data source and it basically just takes that flow from the data source and applies a very very simple transformation of multiplying each number by 10. in a real application you would have repositories that have multiple data sources combine data from those apply complex Transformations and more logic but the general idea would remain the same to visualize this to make a test that can work with the repository that uses flows will create a fake data source to control what values the repository is receiving and then we'll collect the flow that the repository is exposing from our test and make assertions on the values that it produces to do that we need a fake implementation and an easy way to fake a flow is to use just hard-coded values so this will be a cold flow which is why I'm calling this a cold fake data source which whenever it's collected we'll just return one two three and four so testing this we can go to a test method like this which again uses run test because we'll be making suspending calls in there we create the repository passing in our fake data source from here I can use various terminal operators on flows to grab the grab values out of the flow that the repository is exposing so for example I can grab the flow from the repository and call First on it to collect just the first value and assert that the one that the data source produced has actually been multiplied by 10. or I can also collect all of the values from the flow that the repository has into a list assuming that the list that the flow is finite and the entire thing can be collected and then I can make assertions on individual values or the size of the list for example and in cases where I don't want to collect all the values of a flow or the flow is not finite so I couldn't collect all the values of a flow I can use Simple flow operators for example I can take just the first two values of the flow go like those into the list and then make assertions on that list we can also do more Dynamic and more interesting faking of that data source here's a implementation which uses a hot flow internally specifically a mutable share flow so we have a mutable share flow that's stored in our fake data source and this is the flow that the interface method of the data source will return and to control when and what values this emits we have added an emit method on the fake itself that we can use to feed the values into the flow let's see how we can use this so we'll create an instance of the data source we'll pass it into the Repository then I'll create a mutable list here which I'll use as the list where I collect the values that the repository is producing then I will create a new core routine here because I want to collect values from the repository and collect is a suspending call which will only return if the collection is complete and in this case I have a hot flow backing goal of this which will never complete so I don't want to do this in the middle of my test I want it to run concurrently with the rest of the test code that will write so I'm launching a new curtain to do this collection and sorry and I'm using an unconfined test dispatcher here to make sure that I enter this code routine eagerly so by the time that launch returns and we move on to the next line of our test the euchar theme that we created here will already be waiting on the uh line where we are collecting the flow and we'll be ready to process any values that the repository produces I can also make this a bit shorter so instead of using codec directly I can just use tool list which also collects the flow internally and I can specify a Target list where the value should go okay with this setup of echo routine which is ready to collect from the flow I can now start emitting values from the data source which the repository will receive process perform the transformation on it and then my core routine that I started a couple lines earlier will collect it and place it into the list so I can emit from the data source and on the next line I can check that all of this has actually happened and I ended up with the correct value in the list a small problem here is that the current theme that I launched here as I said will never complete because we're launching because we're collecting a hot flow ultimately so our test would hang and timeout eventually because run test usually waits for core routines to complete that we started as children of run test so in order for my test to be able to complete successfully I need to somehow shut down this card routine at the end of the test and one way to do that is to just save the job that launch returned and then cancel it explicitly and at the end of the test like this or with the latest current in apis we also have a property called background scope available on test scope and basically anything you launch in this carotene scope will be canceled automatically at the end of the test so if you have curtains that you know we'll keep going forever and you don't mind that they are still pending at the end of the test and will be canceled you can place those curtains in the scope and for example this is a great way to collect flow values and from here with the setup that we have that collecting protein which will be shut down eventually we can just keep dynamically feeding new values through the data source the repository will process them we collected on the other end and we can keep making assertions against the list of values that we have a quick note here we won't look at its apis in detail but there's also turbine which is a really nice testing utility library for flows and it can for example create that collecting protein for flows for you and then give you some convenient apis to grab the values that it collected and make assertions on it so you might want to check that out to test flows okay uh moving on I want to cover State flows to wrap up things with as they also require extra considerations uh to test them safe flows have this interesting Duality where they are a stakeholder so they have a value which is a current value that they are storing but they are also a stream of values they are a flow which means that they can be collected and collectors will receive the new values that are written into the state flow over time for this example we'll move one layer higher up in our architecture and we'll have a repository interface that's producing a flow of ins and we'll have a view model that depends on that and collects those values so inside that view model here is the state flow that we want to test it's a mutable staple that starts from zero it's exposed to the public as a regular State flow and we have an initialize method inside the viewmodel which launches a new creatine and in there collects the flow from the repository and as values are coming in it's just placing them into the state flow which the UI can then depend on to visualize what's happening here we'll have a fake repository implementation The View model will collect values from the repository and write them into the state flow and then we'll read those values from our test code we'll start with a hot fake implementation here immediately skipping the hard-coded fake that we saw earlier for regular flows because I want to control which values are emitted when during the test so again this is very much the same setup as before we have a mutable shift flow to back the fake implementation and we have a method on the fake to feed values into that share flow for our test we'll assume that we have the main dispatcher rule set up just so that I can use viewmodel scope and I don't have to worry about scheduler sharing within my test but I will move this off of the slide because I need more space but remember that the reason why I'm not handling scheduler sharing here is because I have the main dispatcher set so inside the test we create a fake we pass it into the object under test which is our view model and after that we can already start making assertions for example we can assert on the initial value of the state flow inside the viewmodel then we can call initialize which creates that new protein which will collect values from the repository and as they are coming in writing them into the state flow of the view model after this we can control the repository we can emit values from it by calling the emit method on the fake and we can make assertions on the viewmodel that the value has been collected and placed inside the state flow and we can keep doing this again dynamically controlling which values are coming from the repository and then making assertions at various points on the viewmodel side something important here is that I keep asserting on the current value of the state flow instead of collecting it as a flow and it's our recommendation to treat State flows as stakeholders in your tests wherever you can and control the work that makes changes to that slave flow so that you can keep asserting on the value property instead of having to collect it especially because State flows have a behavior which is that if they receive values quickly in like rapid succession you might not observe all of those new values that have been written into the state flow if you're collecting from it so whether or not you'll see all intermediate values if the status value is changing quickly will depend on how the producing and the consuming coroutines are set up and how fast they are in terms of scheduling um so that's something difficult to handle and control well in tests so instead of having to worry about conflation we recommend just asserting on the value okay I want to make one more note around State flows which has to do with using Statin so I've had this whole setup here in the previous example where viewmodel depends on repository grabs the flow and basically just transforms it into a state flow but I've written a lot of code for it right if you know the stating operator that can actually do the exact same thing with a lot less code so I can just call the method that gives me the flow of the repository cool stating on it and that will do the exact same thing of transforming it into a state flow that I can then read from the UI side what happens to our test if we make this change in the view model well our assertions on the initial value will continue working but if we emit a new value from the Repository and want to check that the view model collected it and updated the value of the state flow that will actually fail so while the initial value is correct uh using Statin is somehow not updating the value and not collecting things from the repository so let's talk about why that happens the reasoning here has to do with the sharing started parameter of Statin so this parameter says that stating will only create its internal correcting that it uses to collect the flow that we called it on when it has a subscriber when someone is collecting this state flow so basically for this correcting to exist which is grabbing things from the repository inside Statin and writing into the view model we also have to have a collector on the test side of things but we are not doing that in our test right I just said that we are not collecting the state flow in the test instead of doing that we just keep reading individual values so um the other curtain will also not exist which would be consuming from the repository and writing into the viewmodel and we just keep reading the same value over and over again from The View model instead of seeing new values at any point also the same would happen if we were using the lazily parameter for sharing started which also only starts that core routine if someone's collecting the state flow so what can we do if we don't want to change our implementation from Statin because Staten is very nice and concise and a lot of people are familiar with it well we can just create a fake collector in this case so just to make sure that we have someone collecting it and that Statin will be active during the test we can launch a new creatine to collect from the state flow and just ignore all of those values so here we have a new cartoon that is collecting the state flow but not consuming the values in any meaningful way and we are again using the background scope API to do this so that this collecting creatine will be shut down automatically at the end of the test and if we run our test again we can now see that this started working all right let's summarize what we learned today so use run test first of all to test coroutines inject dispatchers into your classes to make them testable you can create additional test dispatchers as needed but always always share a single scheduler please uh you can replace the main dispatcher in unit tests by calling satmain which also will simplify sharing schedulers and you can use background scope for curtains like flow collection which you want to be shut down at the end of the test here are some resources around this that we have so we have a curtain testing guide and a flow testing guide in the Android documentation which includes all of this and a lot more so check that out and again if you're working on flows consider using the turbine library for that thank you so much for attending this talk please don't forget to vote for the talk and rate it in the app and you can also find us at the Google Booth if you have any questions I'll try to head there after this talk as well and I think that's it do we have time for questions we have five minutes we have five minutes for questions yeah we can handle a couple of questions hi thanks for for the great talk I wanted to ask back to the view model testing why shouldn't we inject a close-up scope just like you know that will mimic the view modoscope in production and just be a scope and for the test similar to what we do for the repository how you showed okay so so why shouldn't you inject a keratin scope instead of using isotoscopes yeah a closer scope yeah just like the view model scope that mimics in production yeah yeah you can absolutely do that so if you want to uh instead of I mean you know resetting the main dispatcher like we do now sorry can you repeat that instead like the with using the main dispatcher rule like we do now basically just replace that with the injected scope yes yeah so so as I said on the like injecting dispatchers part you can also choose to inject a scope instead and you can do that with view models as well so you can create your own scope for the view model inject it as a Constructor parameter and basically all you have to worry about is making sure that you cancel that Scope when the view model is cleared um which you can also find apis to do now so you can use your own scope instead of view model scope and then you wouldn't necessarily have to set main anymore but you would lose the convenient scheduler sharing like setting Main in a static way is maybe not an ideal way of mocking it but it's very convenient that it helps you with the scheduler sharing part of things so that's like a trade-off to consider there actually thanks um I'm pretty sure I know the inevitable answer to this question but I am curious um if you are if your proteins code interoperates with existing Legacy um Java async Frameworks or the like that don't interact with any of the scheduler stuff can you use any of this can you use any of this if you're interrupting with other asynchronous Solutions uh I don't know uh that's that's a good question to follow up on yeah I think I think we're out of time more or less so thank you very much and okay yeah thank you uh come to the Google booth
Info
Channel: Kotlin by JetBrains
Views: 7,221
Rating: undefined out of 5
Keywords:
Id: nKCsIHWircA
Channel Id: undefined
Length: 42min 49sec (2569 seconds)
Published: Thu May 04 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.