Skip to content

Conversation

@xenonnn4w
Copy link
Contributor

@xenonnn4w xenonnn4w commented Jan 14, 2026

Purpose / Description

Third PR in the CardTemplateEditor migration series.

  • Added tempNotetype property to CardTemplateEditorState
  • Implemented loadNotetype() in ViewModel
  • Implemented updateTemplateContent()
  • Added placeholder addTemplate() and removeTemplate() methods
  • Fragment now shares ViewModel with Activity via activityViewModels()
  • TextWatcher calls ViewModel in parallel with legacy code path during this transitional phase

Links without closing (Part of)

Based on

How Has This Been Tested?

No regressions obtained, and all TemplateEditorViewModelTest passes.

Checklist

Please, go through these checks before submitting the PR.

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@xenonnn4w xenonnn4w added the Blocked by dependency Currently blocked by some other dependent / related change label Jan 14, 2026
Comment on lines +83 to +96
// During ViewPager tab transitions, TextWatcher may fire with a stale ordinal
// (e.g. when switching from a note type with 3 templates to one with 2).
// The legacy code path handles the actual update, so we can safely skip here.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While testing, I encountered a crash when switching between note types with different template counts. The TextWatcher fired during ViewPager tab transitions with a stale cardIndex, causing an IndexOutOfBoundsException i.e. Fixed by adding a bounds check that safely skips the ViewMode update when the ordinal is out of range.

Process: com.ichi2.anki.debug, PID: 21692
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
... 1 more
Caused by: org.json.JSONException: Index 2 out of range [0..2)

Copy link
Member

Choose a reason for hiding this comment

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

We should fix this in the view layer, it should be a bug in the ViewModel

Comment on lines +83 to +96
// During ViewPager tab transitions, TextWatcher may fire with a stale ordinal
// (e.g. when switching from a note type with 3 templates to one with 2).
// The legacy code path handles the actual update, so we can safely skip here.
Copy link
Member

Choose a reason for hiding this comment

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

We should fix this in the view layer, it should be a bug in the ViewModel

Introduce CardTemplateEditorViewModel and CardTemplateEditorState as
the foundation for migrating CardTemplateEditor to use the Rust
backend's atomic updateNotetype() API.

Part 1 of migration, infrastructure only, no behavioral changes yet.
Connect CardTemplateEditorViewModel to CardTemplateEditor Activity:
- Add viewModel declaration using viewModels delegate
- Add state collection with lifecycleScope + repeatOnLifecycle
- Wire tab selection to viewModel.setCurrentTemplateOrd()
- Handle sealed class states (Loading, Loaded, Error, Finished)

Part 2 of migration, infrastructure wiring, no behavioral changes.
…emplate updates

- Add tempNotetype property to Loaded state (sealed class)
- Add loadNotetype() to load and deep clone notetype into ViewModel
- Add updateTemplateContent() using EditorViewType enum
- Add addTemplate() and removeTemplate() placeholder methods
- Wire Fragment's TextWatcher to call ViewModel in parallel with legacy path
- Share ViewModel between Activity and Fragment via activityViewModels()

Part 3 of migration, infrastructure wiring, no behavioral changes.
Comment on lines +270 to +272
showSnackbar(
state.exception.source.localizedMessage ?: getString(R.string.something_wrong),
)
Copy link
Member

@david-allison david-allison Jan 16, 2026

Choose a reason for hiding this comment

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

This feels like it needs a better abstraction.

  • If it's a loading error, a snackbar is insufficient, as it'll be closed when the screen is closed
  • If it's a loading error, this doesn't finish the screen, and it probably should

If it's a transient error (save failed), it may be displayed alongside the valid state

We probably want the user to copy/paste the error, so an error dialog is more friendly

val tempNotetype = loadedState.tempNotetype
if (tempNotetype.templateCount < 2) {
Timber.w("Cannot delete last template")
updateLoadedState { it.copy(message = CardTemplateEditorState.UserMessage.CantDeleteLastTemplate) }
Copy link
Member

Choose a reason for hiding this comment

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

Would be great to test some of this logic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Blocked by dependency Currently blocked by some other dependent / related change Needs Review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants