Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
LongSack
Jan 17, 2003

smiling giraffe posted:

ok try this:
Kotlin code:
    override fun onCreate(savedInstanceState: Bundle?) {
    setContent {
        YourAppTheme {
            val state by mainViewModel.state.collectAsState()
            YourComposable(state)
        }
    }
}
...
I posted this before then got cold feet as I realised it wasn't collecting the flow in a lifecycle aware way. However for the use-case of ui state i think its fine. This is good further reading I think https://manuelvivo.dev/coroutines-addrepeatingjob

No change:
Kotlin code:
        setContent {
            ToDoPlusTheme {
                val navController = rememberNavController()
                val state by mainViewModel.state.collectAsState()
                NavHost(navController = navController, startDestination = "root") {
                    composable("root") {
                        RootPage(
                            state = state,
                            repository = mainViewModel.getToDoFileRepository(),
                            itemCount = mainViewModel.itemCount(),
                            doChoose = mainViewModel.chooseNewList,
                            doNew = mainViewModel.makeNewList,
                            doSearch = mainViewModel.searchItems,
                            doDelete = mainViewModel.deleteItem,
                            doComplete = mainViewModel.completeItem,
                            doEdit = { item -> navController.navigate("edit/${item.id}") }
                        )
                    }
                }
            }
        }

Adbot
ADBOT LOVES YOU

smiling giraffe
Nov 12, 2015

FAT32 SHAMER posted:

Wouldn’t it be lifecycle aware by way of the viewmodel? All it needs to know is if it’s active or not, and it’ll flow as soon as everything comes back after an onResume or w/e

Done like this, I the activity is going to keep collecting even when in the background. There are flow operators like "flowWithLifecycle" that automatically suspend collection of the flow when not in the correct lifecycle state, which makes the flow act in a simlar way to LiveData.

If the activity is collecting the flow to update a view, then this would definately be bad, but if your calling composables I *think* its ok as these don't recompose in the background, but i could be wrong tbh

smiling giraffe
Nov 12, 2015

LongSack posted:

No change:
Kotlin code:
        setContent {
            ToDoPlusTheme {
                val navController = rememberNavController()
                val state by mainViewModel.state.collectAsState()
                NavHost(navController = navController, startDestination = "root") {
                    composable("root") {
                        RootPage(
                            state = state,
                            repository = mainViewModel.getToDoFileRepository(),
                            itemCount = mainViewModel.itemCount(),
                            doChoose = mainViewModel.chooseNewList,
                            doNew = mainViewModel.makeNewList,
                            doSearch = mainViewModel.searchItems,
                            doDelete = mainViewModel.deleteItem,
                            doComplete = mainViewModel.completeItem,
                            doEdit = { item -> navController.navigate("edit/${item.id}") }
                        )
                    }
                }
            }
        }

whats your viewmodel look like now with the StateFlow

FAT32 SHAMER
Aug 16, 2012



smiling giraffe posted:

Done like this, I the activity is going to keep collecting even when in the background. There are flow operators like "flowWithLifecycle" that automatically suspend collection of the flow when not in the correct lifecycle state, which makes the flow act in a simlar way to LiveData.

If the activity is collecting the flow to update a view, then this would definately be bad, but if your calling composables I *think* its ok as these don't recompose in the background, but i could be wrong tbh

Oh derp, yeah if you don’t have composables in a fragment that makes more sense, ty :)

LongSack
Jan 17, 2003

smiling giraffe posted:

whats your viewmodel look like now with the StateFlow

MainViewModel:
Kotlin code:
@HiltViewModel
class MainViewModel @Inject constructor(
    private val toDoRepository: IToDoRepository,
    private val toDoFileRepository: IToDoFileRepository,
) : ViewModel() {

    private val _state = MutableStateFlow(
        ItemState(
            null,
            emptyList(),
            true,
            null
        )
    )
    val state: StateFlow<ItemState>
        get() = _state

    fun getToDoFileRepository(): IToDoFileRepository = toDoFileRepository

    fun isValid() = state.value.currentFile != null

    private fun refreshItems() {
        _state.value = state.value.copy(items = toDoRepository.getItems())
    }

    val chooseNewList: (name: String) -> Unit = { name ->
        Log.i("info", "List names '$name' chosen from drawer")
    }
    val makeNewList: () -> Unit = {
        Log.i("info", "New list button clicked")
    }
    val searchItems: () -> Unit = {
        Log.i("info", "Search button clicked")
    }
    val deleteItem: (item: ToDoItem) -> Unit = { item ->
        Log.i("info", "Delete clicked for ${item.name}")
    }
    val completeItem: (item: ToDoItem) -> Unit = { item ->
        if (!item.complete) {
            var error: String? = null
            val result = toDoRepository.complete(item)
            if (!result.isSuccessResult) {
                error = result.getMessage()
            }
            val newItems = ArrayList<ToDoItem>(toDoRepository.getItems())
            _state.value = state.value.copy(items = newItems, errorMessage = error)
        }
    }

    private fun switchToFile(file: ToDoFile?) {
        if (file == null) {
            _state.value = state.value.copy(
                currentFile = null,
                items = emptyList(),
                isLoading = false,
                errorMessage = "No File Selected"
            )
        } else {
            toDoRepository.load(file)
            refreshItems()
            _state.value = state.value.copy(
                currentFile = file,
                isLoading = false,
                errorMessage = null
            )
        }
    }

    fun switchToFile(listName: String) {
        val file = toDoFileRepository.read(listName)
        switchToFile(file)
    }

    fun itemCount() = state.value.items.size
}

smiling giraffe
Nov 12, 2015
Not sure, looks fine to me. Put it on github if you want I’ll have a look

brand engager
Mar 23, 2011

This isn't related to the bug, but you can remove that backing property for the state by changing to
Kotlin code:
var state by mutableStateOf(
    ItemState(
        null,
        emptyList(),
        true,
        null,
    )
)
private set
from https://developer.android.com/jetpack/compose/state#viewmodels-source-of-truth

LongSack
Jan 17, 2003

smiling giraffe posted:

Not sure, looks fine to me. Put it on github if you want I’ll have a look

https://github.com/vjkrammes/ToDoPlus

Thanks!

smiling giraffe
Nov 12, 2015
The issue is that you're doing IO operations in your repository on the main thread.

If you change your repo functions that access the disk to suspend functions, and then your completeItem function in the viewmodel to this:
Kotlin code:
    val completeItem: (item: ToDoItem) -> Unit = { item ->
        if (!item.complete) {
            viewModelScope.launch {
                withContext(Dispatchers.IO) {
                    var error: String? = null
                    val result = toDoRepository.complete(item)
                    if (!result.isSuccessResult) {
                        error = result.getMessage()
                    }
                    val newItems = ArrayList<ToDoItem>(toDoRepository.getItems())
                    _state.value = state.value.copy(items = newItems, errorMessage = error)
                }
            }
        }
    }
...
it works fine.

LongSack
Jan 17, 2003

smiling giraffe posted:

The issue is that you're doing IO operations in your repository on the main thread.

If you change your repo functions that access the disk to suspend functions, and then your completeItem function in the viewmodel to this:
Kotlin code:
    val completeItem: (item: ToDoItem) -> Unit = { item ->
        if (!item.complete) {
            viewModelScope.launch {
                withContext(Dispatchers.IO) {
                    var error: String? = null
                    val result = toDoRepository.complete(item)
                    if (!result.isSuccessResult) {
                        error = result.getMessage()
                    }
                    val newItems = ArrayList<ToDoItem>(toDoRepository.getItems())
                    _state.value = state.value.copy(items = newItems, errorMessage = error)
                }
            }
        }
    }
...
it works fine.

Welp, I think I made the changes, but it's still not doing anything different.

The real I/O is in the serializer classes (they are internal to the repository classes). I changed all the methods that actually do the I/O to suspend functions, (and of course made the necessary adjustments up the line). Then in the view models where I need to call the repository methods, I did as above. Nothing is different. It still doesn't update the screen until I scroll an item off an on again.

I also started working on the assignees section, using similar code, and it updates just fine (you can only add assignees at the moment, but they do show up and are persisted to the file system).

I updated the git repo to show the code in its current state.

Also, as an aside, in the suspend functions in the serializers, the I/O calls (openXXX, read, write, readline, close, etc.) are all flagged as "inappropriate blocking method call" so that seems a bit off.

smiling giraffe
Nov 12, 2015
yeah you're right its not that. What I had done in addition to adding the coroutine stuff was perform a mapping operation on the result of `getItems()`, which returns a copy of the list, which i think was that actual fix.

So changing "getItems()" function in your repo to this seems to work:

override fun getItems(): List<ToDoItem> = items.toList()

Adding toList() means it returns an immutable copy of the list. So my guess is that by passing around a reference to a mutable list in your StateFlow, the state of the list would change without the StateFlow recognising it. Then when you did post an update to the StateFlow, you're passing it a list which is the same as the existing list, so it doesn't emit a state change as StateFlows only emit if the new state is different to the existing state.

Could be wrong, but its safe to say you want to minimise mutable state as much as possible and don't expose it.

smiling giraffe fucked around with this message at 23:19 on Aug 5, 2022

brand engager
Mar 23, 2011

Why is StateFlow still in the viewmodel? You don't seem to be using the actual flow parts of it. You're also still recreating state objects on every attempted recompose https://github.com/vjkrammes/ToDoPl...Activity.kt#L31, that's gonna cause problems like mentioned before.

LongSack
Jan 17, 2003

smiling giraffe posted:

override fun getItems(): List<ToDoItem> = items.toList()

That seems to have done the trick! Thank you for all your input.

quote:

Why is StateFlow still in the viewmodel? You don't seem to be using the actual flow parts of it. You're also still recreating state objects on every attempted recompose https://github.com/vjkrammes/ToDoPl...Activity.kt#L31, that's gonna cause problems like mentioned before.

Those changes were made by suggestion. Now that toList() is in place, I was able to change the state back to MutableState and can pass the state objects directly from the view models to the composables:
Kotlin code:
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ToDoPlusTheme {
                val navController = rememberNavController()
                NavHost(navController = navController, startDestination = "root") {
                    composable("root") {
                        RootPage(
                            state = mainViewModel.state.value,
                            repository = mainViewModel.getToDoFileRepository(),
                            navController = navController,
                            itemCount = mainViewModel.itemCount(),
                            doChoose = mainViewModel.chooseNewList,
                            doNew = mainViewModel.makeNewList,
                            doSearch = mainViewModel.searchItems,
                            doDelete = mainViewModel.deleteItem,
                            doComplete = mainViewModel.completeItem,
                            doEdit = { item -> navController.navigate("edit/${item.id}") }
                        )
                    }
                    composable("assignees") {
                        AssigneePage(
                            state = assigneeViewModel.state.value,
                            doNew = assigneeViewModel.addAssignee,
                            doDelete = assigneeViewModel.deleteAssignee
                        )
                    }
                }
            }
        }
    }

LongSack
Jan 17, 2003

Next batch of questions -

1. I'm using Android Studio (Chipmunk, 2021.2.1 Patch 1), and several times a day it will just stop responding to the keyboard and the mouse wheel. The mouse itself still works, so I can hit the menu and save all before I quit and restart. Not the worst thing in the world, but it does get annoying because it kills the emulator and the restart time is irritating.

2. I was loading up some of my view models in the init block, but since switching my I/O methods to suspense, I can't do that any more. I'm using a factory method and @Provides (similar to how I would handle it in C#), but I'm wondering if there's a better way:
Kotlin code:
@HiltViewModel
class SettingsViewModel @Inject constructor(
    private val settingsRepository: ISettingsRepository
) : ViewModel() {

...

    fun loadSettings() {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                val settings = settingsRepository.get()
                withContext(Dispatchers.Main) {
                    _state.value = state.value.copy(
                        settings = settings,
                        isLoading = false
                    )
                }
            }
        }
    }
...

    companion object {
        fun create(
            settingsRepository: ISettingsRepository
        ): SettingsViewModel {
            val vm = SettingsViewModel(settingsRepository)
            vm.loadSettings()
            return vm
        }
    }
}

@Provides
fun settingsViewModelFactory(
     settingsRepository: ISettingsRepository
): SettingsViewModel {
    return SettingsViewModel.create(settingsRepository)
}
3. Navigation issue. I'm mostly navigating using a nav menu in the drawer. This works perfectly. However, some navigation is done via clicks, like clicking on a to do item sends you to a page where you can edit the item. Once I do that, it seems like the "root" page get's stuck to that edit page. So, if I click on an item, I go to the edit screen. If I then choose "Settings" from the nav menu, I go to the settings page. If I then choose "Home" from the nav menu, it takes me back to the edit page. If I click the back arrow, then it "unsticks".

I tried changing the code from a simple navController.navigate("edit/${item.id}") to this:
Kotlin code:
                            doEdit = { item ->
                                navController.navigate("edit/${item.id}") {
                                    navController.graph.startDestinationRoute?.let { route ->
                                        popUpTo(route) {
                                            saveState = true
                                        }
                                    }
                                    launchSingleTop = true
                                    restoreState = true
                                }
                            }
And now the "Home" option on the nav menu does take me to the list of items, but from that point no matter which item I click on, I get sent to the edit page for the original item. Only clicking on the back arrow will unstick it.

Ideas? I have updated the git repo

LongSack
Jan 17, 2003

OK, I figured out the navigation thing. My navigation menu is shown below. The saveState was set to true. This was (not surprisingly) causing the state to be retained between page visits. Setting it to false seems to have corrected the problem.
Kotlin code:
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun NavigationDrawer(
    items: List<NavigationMenuItem>,
    navController: NavController,
    scaffoldState: ScaffoldState,
    scope: CoroutineScope,
    title: @Composable (() -> Unit)? = null,
    modifier: Modifier = Modifier,
) {
    val keyboardController = LocalSoftwareKeyboardController.current
    return Column(
        modifier = modifier.then(Modifier.fillMaxWidth())
    ) {
        if (title != null) {
            title()
            Divider(modifier = Modifier.height(4.dp))
        }
        items.forEach { item ->
            NavigationDrawerItem(item = item, selected = false, onItemClick = {
                keyboardController?.hide()
                navController.navigate(item.route) {
                    navController.graph.startDestinationRoute?.let { route ->
                        popUpTo(route) {
                            saveState = false // this was set to true, this was causing the "stickyness" of the pages
                        }
                    }
                    launchSingleTop = true
                    restoreState = false // so was this, but this parameter is not used if saveState is not true
                }
                scope.launch {
                    scaffoldState.drawerState.close()
                }
            })
        }
    }
}

LongSack
Jan 17, 2003

OK, I've made a ton of progress on my todo app. I showed it to a friend a week or so ago, and she said she'd love something where she could organize items by category. So for a shopping list, you could group meat, produce, etc. and have all the things you need in one area of the store all together. So I've rewritten the code to work as a collection of categorized lists, switched to SQLite (using room) instead of JSON, and now have a basic working version of the app.

I have only one thing that's driving me crazy. I'm using Toast (I will be switching to Snackbar) popups for status reporting ("Item created successfully", "Changes saved successfully", etc.) and it's working on every single page except one. I've looked at the working / non-working code side by side in Android Studio, and I can't see any differences. I've checked that the same imports are being used. I've also run it on my Galaxy S22 in case it's an emulator issue, but the behavior is the same.

I've set some breakpoints, and as best as I can tell, the message(s) are being emitted in the viewmodel, but are not being collected in the page. It doesn't matter whether I use Toast or a Snackbar, the message does not show up. If i put some code inline in the composable as a test, the message shows just fine. I've put the code into a new repository here, but here are the parts in question:

ViewModel:
Kotlin code:
private val _toastMessage = MutableSharedFlow<String>()
val toastMessage = _toastMessage.asSharedFlow()

private fun sendToastMessage(message: String) {
    // Log here prints OK
    viewModelScope.launch {
        // Log here prints OK too
        _toastMessage.emit(message)
    }
}
Page:
Kotlin code:
@Composable
fun NewListPage(
    state: NewListState,
    navController: NavController,
    doSetName: (String) -> Unit,
    doToggleImport: () -> Unit,
    doSelectList: (ToDoList) -> Unit,
    doToggleSwitch: () -> Unit,
    doToggleDefault: () -> Unit,
    doSave: (NavController) -> Unit,
    toastMessage: SharedFlow<String>
) {
			...
    val context = LocalContext.current
    LaunchedEffect(Unit) {
        toastMessage
            .collect { message ->
                // Log here prints nothing
                Toast.makeText(context, message, Toast.LENGTH_LONG).show()
            }
    }
			...
and the MainActivity:
Kotlin code:
composable(route = "newlist",
    enterTransition = {
        standardEnterTransition
        fadeIn(animationSpec = tween(300))
    },
    popExitTransition = {
        standardExitTransition
        fadeOut(animationSpec = tween(300))
    }
) {
        NewListPage(
            state = newListViewModel.state.value,
            navController = navController,
            doSetName = newListViewModel::setName,
            doToggleImport = newListViewModel::toggleImport,
            doSelectList = newListViewModel::selectList,
            doToggleSwitch = newListViewModel::toggleSwitch,
            doToggleDefault = newListViewModel::toggleDefault,
            doSave = newListViewModel::create,
            toastMessage = newItemViewModel.toastMessage
    )
}

smiling giraffe
Nov 12, 2015
Have you gone through it step by step in the debugger? That usually works for me

LongSack
Jan 17, 2003

smiling giraffe posted:

Have you gone through it step by step in the debugger? That usually works for me

I have. It’s complicated by the use of coroutines, but setting breakpoints in the viewmodel’s sendToastMessage function shows everything as expected. But setting a breakpoint inside the collect never gets tripped. That’s why i think the root of the problem is that messages are being emitted but not collected, for whatever reason

LongSack
Jan 17, 2003

I fixed the toast problem by rewriting the view model. For some reason, it works using the new view model, even though as best as I can tell the code is identical. Code on the left works, code on the right doesn't:

LongSack
Jan 17, 2003

Next piece of weirdness.

If I have a TextField and set the value to an item in the viewmodel state, then the soft keyboard disappears after every keystroke. The field doesn't lose focus, but the keyboard hides. For example:
Kotlin code:
TextField(
    value = state.name,
    onValueChanged = setName
    ...
)

// in viewmodel
fun setName(value: String) {
    _state.value = state.value.copy(name = value)
}
With this setup, the soft keyboard will hide itself after every keystroke. I'm almost certain that it's related to recomposition. As a workaround, I'm using local state and then updating the viewmodel only when the user presses the save / go / whatever key, like this:
Kotlin code:
val name = remember { mutableStateOf("") }

fun changeName(value: String) {
    name.value = value
}

fun save() {
    setName(name.value) // updates state in view model
    doSave() // invoke the save function in the viewmodel
}
It feels like this duplication of state is not the intended way things should work, but it's the only workaround I've found to make things work.

Ideas?

Small White Dragon
Nov 23, 2007

No relation.
Any recommendations on a cheap device for Android app testing? Cell service is not needed.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
Buy an old Pixel device.

handle
Jan 20, 2011

In Android 13, is it possible to make a pinned shortcut with the icon following the launcher's themed icon setting? Or even a way to query if the default launcher is using themed icons?

brand engager
Mar 23, 2011

Feel like i'm losing my mind, this coroutine runs but never recomposes after the state change
Kotlin code:
@Composable
fun Something() {
    val name by produceState(initialValue = "unknown") {
        delay(5_000)
        value = "some name"
    }
    Text(name)
}
I've put logging in there before to verify that it's running. It should recompose since the Text is observing that state but it never happens

brand engager
Mar 23, 2011

Another weird thing, I added this below that Text
Kotlin code:
if (name != "unknown") Text(name)
This one does appear when the state updates, but the original Text still shows the old state

FAT32 SHAMER
Aug 16, 2012



Put the text inside of the productState lambda?

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.
I have an app on Google Play, Word Topics: Unscramble Words.

But if you search for Word Topics you won't see it: https://play.google.com/store/search?q=word%20topics&c=apps

Ditto if you search for the entire name: https://play.google.com/store/search?q=https%3A%2F%2Fplay.google.com%2Fstore%2Fsearch%3Fq%3Dword%2520topics%26c%3Dapps&c=apps

The actual app is here: https://play.google.com/store/apps/details?id=com.oldeskuul.wordtopics

What in the name of all that is holy is going on here?

PokeJoe
Aug 24, 2004

hail cgatan


Welcome to the wonderful world of the google play store lol. It's been like that for years

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.

PokeJoe posted:

Welcome to the wonderful world of the google play store lol. It's been like that for years

I never encountered it before. Been on Android since the start too.

Don't they have people who know how to write a search engine :-)

This reminds me of Google Plus. Company is so "Stove Pipped" that they launched Plus without social graph API's for mobile.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...

VideoGameVet posted:

I have an app on Google Play, Word Topics: Unscramble Words.

But if you search for Word Topics you won't see it: https://play.google.com/store/search?q=word%20topics&c=apps

Ditto if you search for the entire name: https://play.google.com/store/search?q=https%3A%2F%2Fplay.google.com%2Fstore%2Fsearch%3Fq%3Dword%2520topics%26c%3Dapps&c=apps

The actual app is here: https://play.google.com/store/apps/details?id=com.oldeskuul.wordtopics

What in the name of all that is holy is going on here?

You're not including "unscramble words" in your search. Sucks that it doesn't want to do a partial match.

Only registered members can see post attachments!

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
And, not sure if this is debugging left on, but please do not slide yourself into the clipboard unsolicited...

"User7001 Used 0 Hints To Complete "Sample 001" In Word Topics"

E: very cool, you also do not allow me to continue without copying this into the clipboard.

Volmarias fucked around with this message at 09:44 on Jan 30, 2023

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
... you also do not allow backing out of dialogs in general, it seems.

Only registered members can see post attachments!

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.
Good catches. I may need to find a new team because they have been AWOL for over a week. I have source.

This thing has been in development for too long.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
The "please wait" spinner isn't even centered when it rotates. The profile picture is squished. There's probably plenty of other stuff too.

The game part of it is fine, the whole thing just has a half-finished look. If you paid someone to develop this in the hopes of making money, you might want to think about whether any future work is just throwing good money after bad.

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.

Volmarias posted:

The "please wait" spinner isn't even centered when it rotates. The profile picture is squished. There's probably plenty of other stuff too.

The game part of it is fine, the whole thing just has a half-finished look. If you paid someone to develop this in the hopes of making money, you might want to think about whether any future work is just throwing good money after bad.

Well, we fixed the name thing.

https://play.google.com/store/search?q=wordtopics&c=apps

And yeah, I'm going to have to spend more to get all of this polished, I'm just trying to see if it's worth it.

It is my design and this is the 3rd group of developers who have been on it.

brand engager
Mar 23, 2011

Anyone used mockk to mock extension functions on a singleton object before? I cant figure out any combination of mockkStatic or mockkObject that works. The specific thing I need to mock is google's firebase initialization, they've got it declared like
Kotlin code:
object Firebase

fun Firebase.initialize(context: Context): FirebaseApp? = FirebaseApp.initializeApp(context)

//some overloads below of that function that I'm not trying to mock for our tests
The documentation is this huge page with a few examples but it's not good https://mockk.io/#extension-functions
I don't know why google made those extension functions in the same file instead of just being part of the object either, real pain in the rear end. FirebaseApp is in java instead of kotlin

brand engager
Mar 23, 2011

Forgot they have the source on github, this is the specific function I was trying to mock for tests
https://github.com/firebase/firebas...Firebase.kt#L48

FAT32 SHAMER
Aug 16, 2012



I have never mocked context before because those are usually called from a view, service, or application subclass, and we only unit test those via UIAutomator. I’m not sure if you’re using DI in your app, but you may need to mock the repository that is the intermediate between your firebase api call and the view you’re unit testing, or build a dummy class with fake data in your setup function

Tldr if you’re trying to mock a function that takes context as an arg, you’re probably doing something wrong

brand engager
Mar 23, 2011

I'm trying to mock the initialize function, not a context param

Adbot
ADBOT LOVES YOU

FAT32 SHAMER
Aug 16, 2012



You’re going to need to mock context to call the initialiser extension, unless you linked the wrong one

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply