Skip to content

Conversation

@ThomasSession
Copy link
Collaborator

Adds a new debug logger functionality.
It allows certain logs to be grouped into known categories, which can be set to pop up as toasts to be visible in situ in the app as it happens.
The new feature also keeps an in-memory cache of these logs which can be seen in a new screen.
The logs can be filtered, copied, and individually copied per log with a long press

Screenshot_1762467195 Screenshot_1762466666 Screenshot_1762466674

Comment on lines 50 to 55
(it + DebugLogData(
message = message,
group = group,
date = date,
formattedDate = dateUtils.getLocaleFormattedTime(date.toEpochMilli())
)).sortedByDescending { log -> log.date }
Copy link
Collaborator

@SessionHero01 SessionHero01 Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use some optimization here, given we can potentially use this log a lot. A few things to consider:

  1. We might not want to create two new lists everytime you log an item
  2. We might want to limit the size of the memory log

I think it's probably better to use a mutable structure here. So you'd have to have setup like:

val logs = Deque<DebugLogData>() // The mutable data structure to store logs
val logNotification = MutableSharedFlow()

fun addLog(item: DebugLogData) {
  synchronized(logs) {
     logs.addLast(item)
     while (logs.size > 100) {
        logs.popFirst()
     }
  }

 logNotification.tryEmit(Unit)
}

Then, on the UI side, you'll then listen to the logNotification, and you'll need to copy the logs to a list with a lock when displaying on the UI. This way we won't have a very high overhead when appending logs, and we will have a slower log viewer screen but that's ok it's a debug only facility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yep it definitely needs to be limited. I wanted to add that in and it slipped my mind as I implemented it.

@SessionHero01
Copy link
Collaborator

I wonder if it's better to just tap into our existing Logger facility - it's already a well abstracted structure

Comment on lines 54 to 55
val logSnapshots: Flow<List<DebugLogData>> =
logChanges.onStart { emit(Unit) }.map { currentSnapshot() }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to do this:

Suggested change
val logSnapshots: Flow<List<DebugLogData>> =
logChanges.onStart { emit(Unit) }.map { currentSnapshot() }
val logSnapshots: Flow<List<DebugLogData>> get() =
logChanges.onStart { emit(Unit) }.map { currentSnapshot() }

As the flow shouldn't be reused

message = text,
group = group,
date = now,
formattedDate = dateUtils.getLocaleFormattedTime(now.toEpochMilli())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just store the date, and let the UI do the formatting

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SessionHero01 That's how I initially had it setup but our DateUtil class needs to be injected so can't be in a composable

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we pass DateUtils around in compose?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but is there an advantage? The VM can have the responsibility of formatting and manage the exact data the UI will need. Or do you think it's better done in Compose?
I think it might be cleaner to have that formatting in the VM

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be in the VM - I just find it wasteful to have it computed but no one is using it here in the logger.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah right, you mean calculating it on every log while it might never be displayed. Ok I'll see if there's a clean way to pass it to the composable

logChanges.tryEmit(Unit)

// Toast decision is independent from capture.
if (getGroupToastPreference(group)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting a preference is not free (it can involve I/O), i wonder if we should cache it somewhere

viewModel: DebugMenuViewModel,
onBack: () -> Unit,
){
val logs by viewModel.debugLogs.collectAsStateWithLifecycle(initialValue = emptyList())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might have to become:

Suggested change
val logs by viewModel.debugLogs.collectAsStateWithLifecycle(initialValue = emptyList())
val logs by remember { viewModel.debugLogs }.collectAsStateWithLifecycle(initialValue = emptyList())

@ThomasSession ThomasSession merged commit f1eba16 into dev Nov 7, 2025
4 checks passed
@ThomasSession ThomasSession deleted the feature/debug-logger branch November 7, 2025 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants