{"record":{"id":"453531b7fb3f1a73","repo":"AvaloniaUI/Avalonia","slug":"this-class-should-be-instanciated-from-the-ui-thre","errorCode":null,"errorMessage":"This class should be instanciated from the UI thread","messagePattern":"This class should be instanciated from the UI thread","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Android/Avalonia.Android/AndroidDispatcherImpl.cs","lineNumber":33,"sourceCode":"    {\n        [ThreadStatic] private static bool? s_isUIThread;\n        private readonly Looper _mainLooper;\n        private readonly Handler _handler;\n        private readonly Runnable _signaler;\n        private readonly Runnable _timerSignaler;\n        private readonly Runnable _wakeupSignaler;\n        private readonly MessageQueue _queue;\n        private int _signaled;\n        private bool _backgroundProcessingRequested;\n        \n\n        public AndroidDispatcherImpl()\n        {\n            _mainLooper = App.Context.MainLooper ??\n                          throw new InvalidOperationException(\n                              \"Application.Context.MainLooper was not expected to be null.\");\n            if (!CurrentThreadIsLoopThread)\n                throw new InvalidOperationException(\"This class should be instanciated from the UI thread\");\n            _handler = new Handler(_mainLooper);\n            _signaler = new Runnable(OnSignaled);\n            _timerSignaler = new Runnable(OnTimer);\n            _wakeupSignaler = new Runnable(() => { });\n            _queue = Looper.MyQueue();\n            Looper.MyQueue().AddIdleHandler(new IdleHandler(this));\n            CanQueryPendingInput = OperatingSystem.IsAndroidVersionAtLeast(23);\n        }\n        \n        public event Action? Timer;\n        private void OnTimer() => Timer?.Invoke();\n\n        public event Action? Signaled;\n        private void OnSignaled()\n        {\n            Interlocked.Exchange(ref _signaled, 0);\n            Signaled?.Invoke();\n        }","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/AvaloniaUI/Avalonia/blob/11c542726898ae954a1ef668c65ec79ec92ab17d/src/Android/Avalonia.Android/AndroidDispatcherImpl.cs#L15-L51","documentation":"AndroidDispatcherImpl must be created on the Android UI (main Looper) thread because it posts Runnables to that Looper. The constructor checks CurrentThreadIsLoopThread and throws InvalidOperationException if not. This prevents a dispatcher bound to the wrong thread from the start.","triggerScenarios":"Constructing AndroidDispatcherImpl from a background/worker thread or from native code where the current thread is not the main Looper thread. Happens if the platform is initialized off the UI thread (e.g. from a Task, service, or JNI callback thread).","commonSituations":"Initializing Avalonia in a background thread; calling from a Task continuation or a Timer; embedding AvaloniaAndroid into a host that spawns the dispatcher from a worker; misuse in instrumentation that runs off-main.","solutions":["Initialize the AndroidDispatcherImpl (and the Avalonia Android platform) on the main UI thread (Activity.onCreate / Application.onCreate).","If you must start from another thread, post the initialization to Looper.MainLooper via a Handler.","Verify CurrentThreadIsLoopThread is true before constructing.","Audit JNI/native callbacks that may run on binder threads and marshal to the UI thread."],"exampleFix":"// before\nif (!CurrentThreadIsLoopThread)\n    throw new InvalidOperationException(\"This class should be instanciated from the UI thread\");\n\n// after (assert on the looper with a clearer message; typo fixed)\nif (Looper.MyLooper() != Looper.MainLooper)\n    throw new InvalidOperationException(\n        \"AndroidDispatcherImpl must be created on the main UI thread (got thread '\" + Environment.CurrentManagedThreadId + \"').\");","handlingStrategy":"validation","validationCode":"// marshal dispatcher construction to the UI thread\nif (Looper.MyLooper() != Looper.MainLooper)\n    new Handler(Looper.MainLooper!).Post(() => new AndroidDispatcherImpl());","typeGuard":"static bool OnUiThread() => Looper.MyLooper() == Looper.MainLooper;","tryCatchPattern":"try { _dispatcher = new AndroidDispatcherImpl(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"UI thread\"))\n{ /* re-dispatch to main looper and retry */ }","preventionTips":["Construct the dispatcher on the main UI thread only.","Post initialization via Handler(Looper.MainLooper) if starting elsewhere.","Audit JNI/native callback threads."],"tags":["android","dispatcher","ui-thread","looper","platform","message-typo"],"backgroundTag":null,"analyzedSha":"11c542726898ae954a1ef668c65ec79ec92ab17d","analyzedAt":"2026-08-13T11:57:40.261Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}