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
smiling giraffe
Nov 12, 2015

Shadow0 posted:

Does anyone know how to use Android's Room (API?)?
I've been doing a lot with it, but I can't figure out how to do things with files - specifically I want to be able to save a backup of the database or a subset of the database, or merge one into another (i.e. export and import).
I can't seem to find any info on it, and I'm not tremendously knowledgeable on how databases work - in case there's a more pure SQL solution.

What are you trying to acheive by doing those things?

Adbot
ADBOT LOVES YOU

smiling giraffe
Nov 12, 2015

Shadow0 posted:

I'm keeping track of my expenses with my app, and I want to be able to copy the database as a backup. But I also want the option to import a database for the opposite reason. Being able to do it for a particular month or other grouping would also be nice.
I suppose I could just export/import it as a CSV file or something.

I was wondering if you could do what you wanted with some meta-data fields, like adding a import_id field to each table, and another table to keep track of which import is current, etc. This you could do in Room, but I can see it getting really annoying if your table schema is anything beyond basic, and it will add overheard to future changes.

To do what you originally asked though, I think you're going to want to use, https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase

smiling giraffe
Nov 12, 2015

Sistergodiva posted:

...a problem...

baka kaba posted:

...a possible solution...

Did you try this? Did it work?

smiling giraffe
Nov 12, 2015
It could be a bunch of things. Have you got a git repo to look at?

As a general point Ive been loving Compose after years of recyclerview bullshit. I appreciate it’s probably not practical to switch for whatever you’re working on now, but seriously consider it for future stuff.

smiling giraffe
Nov 12, 2015

Anne Bonny posted:

https://github.com/kraftski/RecyclerViewError

Here we go. I made a small project that demonstrates the error. There are some screenshots in the readme. You press the center button to load some images from your device and the program loads the images into two recyclerviews, the one on the left which loads properly, and the one on the right which scrolls up as new images are loaded into it. You'll see that I've made it so that the later images are in the dataset, the more of a green tint they have to them. You can see what I mean in the error screenshot, which also demonstrates the error kind of. It's not that the recyclerview on the right simply scrolls a little bit once after you load your images, it continually scrolls at any time images are loaded. Very frustrating and confusing.

The files which are of interest here are: MainActivity.java, activity_main.xml, ImageAdapter.java.

Got it to work by replacing the right hand RelativeLayout with a ConstraintLayout, like this:

code:
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <View
                android:id="@+id/lower"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:background="@color/purple_700"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/right"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_above="@id/lower"
                android:background="@color/purple_200"
                app:layout_constraintBottom_toTopOf="@+id/lower"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:reverseLayout="true" />
        </androidx.constraintlayout.widget.ConstraintLayout>
However my experience of the problem was different to how you describe. I only saw the scroll immediately after the images were loaded, and it seemed to be related to that bottom purple padding view... because if you set the height of that to 0dp then the problem disapeared. It was like the recycler thought it had the whole screen height to work with, but then had to compensate for the bottom padding so it shrunk itself without adjusting it's viewport. I saw this behaviour regardless of whether the item was an image or just a empty view.

ConstraintLayouts are generally better then RelativeLayouts though, they are more powerful and you don't need to nest views so much, which is bad for performance.

smiling giraffe
Nov 12, 2015
Java and RecyclerView are old hat in the Android world now.

I don't know what time/cost constraints you are operating under, but long term, adopting kotlin and compose will make your Android dev experience a lot more enjoyable.

smiling giraffe
Nov 12, 2015
Yeah you can, it’s kotlin, functions don’t need to be a class

smiling giraffe
Nov 12, 2015
“be in a class”* even, but yeah, kotlin is great and apart from a few minor things, compose is great, and infinitely better than recyclerviews

smiling giraffe
Nov 12, 2015
You want to hold state in a viewmodel. This is an Android architecture component that is designed to persist as activities move through their lifecycle.

View models typically expose their state using some implementation of the observable pattern, historically this has been livedata, but more recently StateFlow. This allows activities/fragments to observe and react to state when they are ready to do so.

There should be plenty of good guides out there for getting setup with a viewmodel

smiling giraffe
Nov 12, 2015
I think you want your viewmodel to expose your UI state in a StateFlow, which is collected in your activity when its in the appropriate lifecycle state. See: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow#stateflow

I think the State class you are using in the viewmodel only causes recomposition within the context of a composable function.

More generally you shouldn't need to inject your repo into the activity and pass it to the composables.

Giving the Activity the Repo you're letting it know to much, the Activity should just be like: "here is some ui state from the viewmodel that I will observe. when a button is clicked i will tell the viewmodel it happened". Then it's the viewmodels job to decide how that button click should result in a change of state.

Also not sure why you are wrapping ToDoItem in a MutableState

smiling giraffe
Nov 12, 2015

LongSack posted:

OK based on that page, I changed the state to a (Mutable)StateFlow. Making that change alone didn't change anything. Then I noticed the part about the change in the onCreate method, so I added that:
Kotlin code:
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mainViewModel.state.collect { state ->
			// WHAT GOES HERE?
                }
            }
        }
...
I can't call a composable function there. I tried moving the entire setContent block into that place, and again -- while it works, the items are still not being updated on the screen until scrolled off and back on again. What am I missing?

try this:

nm :)

smiling giraffe fucked around with this message at 17:32 on Aug 4, 2022

smiling giraffe
Nov 12, 2015
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

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

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

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.

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

Adbot
ADBOT LOVES YOU

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

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