-
Notifications
You must be signed in to change notification settings - Fork 22
ADFA-3829 Clear StrictMode violations on create-init flow #1277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: stage
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ package com.itsaky.androidide.fragments | |
|
|
||
| import android.os.Bundle | ||
| import android.view.View | ||
| import androidx.lifecycle.lifecycleScope | ||
| import org.koin.androidx.viewmodel.ext.android.activityViewModel | ||
| import androidx.recyclerview.widget.LinearLayoutManager | ||
| import androidx.transition.TransitionManager | ||
|
|
@@ -33,6 +34,7 @@ import com.itsaky.androidide.idetooltips.TooltipTag.SETUP_OVERVIEW | |
| import com.itsaky.androidide.idetooltips.TooltipTag.SETUP_PREVIOUS | ||
| import com.itsaky.androidide.roomData.recentproject.RecentProject | ||
| import com.itsaky.androidide.tasks.executeAsyncProvideError | ||
| import com.itsaky.androidide.templates.ParameterWidget | ||
| import com.itsaky.androidide.templates.ProjectTemplateRecipeResult | ||
| import com.itsaky.androidide.templates.StringParameter | ||
| import com.itsaky.androidide.templates.Template | ||
|
|
@@ -41,6 +43,9 @@ import com.itsaky.androidide.utils.TemplateRecipeExecutor | |
| import com.itsaky.androidide.utils.flashError | ||
| import com.itsaky.androidide.utils.flashSuccess | ||
| import com.itsaky.androidide.viewmodel.MainViewModel | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.launch | ||
| import kotlinx.coroutines.withContext | ||
|
|
||
| /** | ||
| * A fragment which shows a wizard-like interface for creating templates. | ||
|
|
@@ -160,7 +165,21 @@ class TemplateDetailsFragment : | |
| private fun bindWithTemplate(template: Template<*>?) { | ||
| template ?: return | ||
|
|
||
| binding.widgets.adapter = TemplateWidgetsListAdapter(template.widgets) | ||
| binding.title.text = template.templateNameStr | ||
|
|
||
| // Some parameters do disk work in their beforeCreateView hook (e.g. computing a | ||
| // non-colliding default project name). Run those hooks on Dispatchers.IO before | ||
| // attaching the adapter so that onBindViewHolder skips them (they are one-shot). | ||
| viewLifecycleOwner.lifecycleScope.launch { | ||
| withContext(Dispatchers.IO) { | ||
| template.widgets.forEach { widget -> | ||
| if (widget is ParameterWidget<*>) { | ||
| widget.parameter.beforeCreateView() | ||
| } | ||
| } | ||
| } | ||
| _binding ?: return@launch | ||
| binding.widgets.adapter = TemplateWidgetsListAdapter(template.widgets) | ||
| } | ||
|
Comment on lines
+173
to
+183
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent stale adapter assignment from overlapping bind jobs Line 173 launches a new coroutine per template update, but previous jobs are not canceled. A slower old job can complete later and assign widgets for the wrong template. Suggested fix+import kotlinx.coroutines.Job
...
class TemplateDetailsFragment : ... {
+ private var bindTemplateJob: Job? = null
...
private fun bindWithTemplate(template: Template<*>?) {
template ?: return
binding.title.text = template.templateNameStr
- viewLifecycleOwner.lifecycleScope.launch {
+ bindTemplateJob?.cancel()
+ bindTemplateJob = viewLifecycleOwner.lifecycleScope.launch {
withContext(Dispatchers.IO) {
template.widgets.forEach { widget ->
if (widget is ParameterWidget<*>) {
widget.parameter.beforeCreateView()
}
}
}
_binding ?: return@launch
binding.widgets.adapter = TemplateWidgetsListAdapter(template.widgets)
}
}
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ package com.itsaky.androidide.fragments | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.os.Bundle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.view.View | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.content.res.Configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.lifecycleScope | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.koin.androidx.viewmodel.ext.android.activityViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.recyclerview.widget.GridLayoutManager | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.R | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,10 +29,14 @@ import com.itsaky.androidide.databinding.FragmentTemplateListBinding | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.idetooltips.TooltipManager | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.idetooltips.TooltipTag.EXIT_TO_MAIN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.templates.ITemplateProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.templates.ITemplateWidgetViewProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.templates.ProjectTemplate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.templates.impl.TemplateProviderImpl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.utils.flashError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.viewmodel.MainViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.Dispatchers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.withContext | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.slf4j.LoggerFactory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -117,36 +122,46 @@ class TemplateListFragment : | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.debug("Reloading templates...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val provider = ITemplateProvider.getInstance(reload = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val templates = provider.getTemplates().filterIsInstance<ProjectTemplate>() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val warnings = (provider as? TemplateProviderImpl)?.warnings.orEmpty() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adapter = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TemplateListAdapter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| templates = templates, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick = { template, _ -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.template.value = template | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_DETAILS) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLongClick = { template, itemView -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| template.tooltipTag?.let { tag -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TooltipManager.showIdeCategoryTooltip( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context = requireContext(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anchorView = itemView, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tag = tag | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.list.adapter = adapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateSpanCount() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewLifecycleOwner.lifecycleScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val (templates, warnings) = withContext(Dispatchers.IO) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val provider = ITemplateProvider.getInstance(reload = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Pre-warm the widget view provider so per-bind getInstance() in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TemplateWidgetsListAdapter doesn't trigger a disk read on the UI thread. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ITemplateWidgetViewProvider.getInstance() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val templates = provider.getTemplates().filterIsInstance<ProjectTemplate>() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val warnings = (provider as? TemplateProviderImpl)?.warnings.orEmpty() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| templates to warnings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (warnings.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requireActivity().flashError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warnings.joinToString(System.lineSeparator()) { w -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requireContext().getString(w.resId, *w.args.toTypedArray()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _binding ?: return@launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adapter = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TemplateListAdapter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| templates = templates, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick = { template, _ -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.template.value = template | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_DETAILS) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLongClick = { template, itemView -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| template.tooltipTag?.let { tag -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TooltipManager.showIdeCategoryTooltip( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context = requireContext(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anchorView = itemView, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tag = tag | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| binding.list.adapter = adapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateSpanCount() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (warnings.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requireActivity().flashError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warnings.joinToString(System.lineSeparator()) { w -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requireContext().getString(w.resId, *w.args.toTypedArray()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+125
to
+166
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cancel previous reload coroutine to avoid out-of-order UI updates Line 125 starts a new reload every time Suggested fix+import kotlinx.coroutines.Job
...
class TemplateListFragment : ... {
+ private var reloadJob: Job? = null
...
private fun reloadTemplates() {
_binding ?: return
log.debug("Reloading templates...")
- viewLifecycleOwner.lifecycleScope.launch {
+ reloadJob?.cancel()
+ reloadJob = viewLifecycleOwner.lifecycleScope.launch {
val (templates, warnings) = withContext(Dispatchers.IO) {
...
}
_binding ?: return@launch
...
}
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| package com.itsaky.androidide.lsp.java.debug | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import android.net.TrafficStats | ||||||||||||||||||||||||||||||||||||||||||||
| import com.itsaky.androidide.lsp.debug.IDebugClient | ||||||||||||||||||||||||||||||||||||||||||||
| import com.sun.jdi.VirtualMachine | ||||||||||||||||||||||||||||||||||||||||||||
| import com.sun.jdi.connect.Connector | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,6 +15,11 @@ internal data class ListenerState( | |||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||
| private val invalidated = AtomicBoolean(false) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private companion object { | ||||||||||||||||||||||||||||||||||||||||||||
| // Tag used to identify the JDWP listener socket for traffic-stats accounting. | ||||||||||||||||||||||||||||||||||||||||||||
| private const val JDWP_LISTENER_SOCKET_TAG = 0x444A574C // "JDWL" | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||
| * Whether we're currently listening for incoming connections. | ||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -39,7 +45,12 @@ internal data class ListenerState( | |||||||||||||||||||||||||||||||||||||||||||
| * @return The address of the listening socket. | ||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
| fun startListening(): String { | ||||||||||||||||||||||||||||||||||||||||||||
| val address = connector.startListening(args) | ||||||||||||||||||||||||||||||||||||||||||||
| TrafficStats.setThreadStatsTag(JDWP_LISTENER_SOCKET_TAG) | ||||||||||||||||||||||||||||||||||||||||||||
| val address = try { | ||||||||||||||||||||||||||||||||||||||||||||
| connector.startListening(args) | ||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||
| TrafficStats.clearThreadStatsTag() | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: The Android TrafficStats class provides methods for per-thread network traffic tagging to track socket traffic statistics. getThreadStatsTag: Returns the active tag (int) used for accounting Socket traffic from the current thread. Only one tag per thread is supported. Added in API level 14 [1][2]. clearThreadStatsTag: Clears the active thread stats tag for the current thread. Added in API level 8 [1][2][3]. These work with setThreadStatsTag(int tag) to set the tag (API level 8) [1][2], and tagSocket(Socket) to apply it to sockets. Tags help profile app network usage by category [4][5]. Example usage for HTTP requests [5]: Citations:
🌐 Web query:
💡 Result: In Android's TrafficStats API, getThreadStatsTag returns the active thread stats tag used for accounting socket traffic from the current thread. The return value of -1 indicates no tag is set for the thread, as this is the default initial value in the ThreadLocal storage (UidTag.tag = -1).[1][2] This differs from TrafficStats.UNSUPPORTED (-1), which is returned by network statistic methods when the device does not support the requested statistic.[3][4][5][6] Official docs describe getThreadStatsTag as returning the "active tag" without specifying -1 explicitly as "no tag," but source code confirms -1 as the unset/default state, and developers use clearThreadStatsTag to explicitly reset to this state.[4][1] Citations:
Preserve and restore any pre-existing thread stats tag
💡 Suggested patch fun startListening(): String {
- TrafficStats.setThreadStatsTag(JDWP_LISTENER_SOCKET_TAG)
+ val previousTag = TrafficStats.getThreadStatsTag()
+ TrafficStats.setThreadStatsTag(JDWP_LISTENER_SOCKET_TAG)
val address = try {
connector.startListening(args)
} finally {
- TrafficStats.clearThreadStatsTag()
+ if (previousTag == -1) {
+ TrafficStats.clearThreadStatsTag()
+ } else {
+ TrafficStats.setThreadStatsTag(previousTag)
+ }
}
listenAddress = address
return address
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| listenAddress = address | ||||||||||||||||||||||||||||||||||||||||||||
| return address | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -157,6 +157,7 @@ abstract class Parameter<T>( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.actionBeforeCreateView = null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.actionAfterCreateView = null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.beforeCreateViewInvoked = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun clearObservers() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -185,10 +186,20 @@ abstract class Parameter<T>( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.actionBeforeCreateView = action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private var beforeCreateViewInvoked = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Called before the layout for this widget is created. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Called before the layout for this widget is created. The action registered via | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * [doBeforeCreateView] is invoked at most once per parameter instance — callers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * may pre-invoke this off the UI thread (e.g. before binding a RecyclerView) so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * that the bind-time call is a no-op and avoids triggering disk reads on the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * main thread. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| open fun beforeCreateView() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (beforeCreateViewInvoked) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeCreateViewInvoked = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.actionBeforeCreateView?.invoke(this) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+189
to
203
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard one-shot Line 199-Line 203 performs a non-atomic check/set on Suggested fix- private var beforeCreateViewInvoked = false
+ private var beforeCreateViewInvoked = false
open fun beforeCreateView() {
- if (beforeCreateViewInvoked) {
- return
- }
- beforeCreateViewInvoked = true
- this.actionBeforeCreateView?.invoke(this)
+ val action = lock.withLock {
+ if (beforeCreateViewInvoked) return
+ beforeCreateViewInvoked = true
+ actionBeforeCreateView
+ }
+ action?.invoke(this)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,13 @@ import com.itsaky.androidide.templates.impl.databinding.LayoutTextfieldBinding | |
| import com.itsaky.androidide.utils.ServiceLoader | ||
| import com.itsaky.androidide.utils.SingleTextWatcher | ||
| import androidx.core.graphics.drawable.toDrawable | ||
| import androidx.lifecycle.findViewTreeLifecycleOwner | ||
| import androidx.lifecycle.lifecycleScope | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.Job | ||
| import kotlinx.coroutines.delay | ||
| import kotlinx.coroutines.launch | ||
| import kotlinx.coroutines.withContext | ||
|
|
||
|
|
||
| /** | ||
|
|
@@ -66,6 +73,8 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider { | |
| @JvmStatic | ||
| private var service: ITemplateWidgetViewProvider? = null | ||
|
|
||
| private const val VALIDATION_DEBOUNCE_MS = 150L | ||
|
|
||
| @JvmStatic | ||
| fun getInstance(reload: Boolean = false): ITemplateWidgetViewProvider { | ||
| if (reload) { | ||
|
|
@@ -149,20 +158,38 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider { | |
| } | ||
| } | ||
|
|
||
| var validationJob: Job? = null | ||
|
|
||
| param.configureTextField(context, root) { value -> | ||
| observer.disableAndRun { | ||
| param.setValue(value) | ||
| param.resetStartAndEndIcons(root.context, root) | ||
| } | ||
|
|
||
| val err = | ||
| ConstraintVerifier.verify(value, constraints = param.constraints) | ||
|
|
||
| root.isErrorEnabled = err != null | ||
| if (err != null) { | ||
| root.error = err | ||
| // The EXISTS / FILE / DIRECTORY constraints stat the filesystem; running them | ||
| // synchronously on the UI thread would trigger a StrictMode DiskReadViolation | ||
| // on every keystroke. Debounce + dispatch to IO; apply the result on Main. | ||
| validationJob?.cancel() | ||
| val owner = root.findViewTreeLifecycleOwner() | ||
| validationJob = owner?.lifecycleScope?.launch { | ||
| delay(VALIDATION_DEBOUNCE_MS) | ||
| val err = withContext(Dispatchers.IO) { | ||
| ConstraintVerifier.verify(value, constraints = param.constraints) | ||
| } | ||
| root.isErrorEnabled = err != null | ||
| if (err != null) { | ||
| root.error = err | ||
| } | ||
| } ?: run { | ||
| // Fall back to synchronous validation when no lifecycle owner is attached | ||
| // (e.g., previews/tests). Production attaches a fragment lifecycle. | ||
| val err = ConstraintVerifier.verify(value, constraints = param.constraints) | ||
| root.isErrorEnabled = err != null | ||
| if (err != null) { | ||
| root.error = err | ||
| } | ||
|
Comment on lines
+179
to
+190
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clear error text when validation passes At Line 179-Line 190, Suggested fix- root.isErrorEnabled = err != null
- if (err != null) {
- root.error = err
- }
+ root.isErrorEnabled = err != null
+ root.error = err🤖 Prompt for AI Agents |
||
| null | ||
| } | ||
|
|
||
| } | ||
|
|
||
| input.setText(param.value) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tighten this rule to the NotificationManager call path.
Right now this matcher allows any stack containing the 3-frame MIUI chain, even if it is not triggered from notification delivery. Narrow it by also requiring the downstream
NotificationManager.notify/notifyAsUseranchor in order.Suggested narrowing
🤖 Prompt for AI Agents