ARTICLE AD BOX
This is the big issue i am facing and tried to research everywhere but not working for me please if anyone can resolve it for me
I simply tried to call api in the custom keyboard like when text is selected and tap on any button for ex- Check grammer then api should call according to my code and show the result in keyboard where letters show there will show response when api is called it shows infinite loading and nothing prints logs and nothing shows i don't even know what is the exact problem
RewriterkeyboardService.kt
private const val CHANNEL = "keyboard_api" private const val TAG = "KeyboardService" class RewriterKeyboardService : InputMethodService() { private lateinit var methodChannel: MethodChannel private var inputConnection: InputConnection? = null private lateinit var flutterEngine: FlutterEngine private lateinit var keyboardView: View private var isFlutterReady = false private val pendingCalls = mutableListOf<Pair<String, String>>() private var isShiftOn = false private var isCapsLockOn = false private val letterKeys = listOf( "Q","W","E","R","T","Y","U","I","O","P", "A","S","D","F","G","H","J","K","L", "Z","X","C","V","B","N","M" ) // ================= LIFECYCLE ================= override fun onStartInputView(info: EditorInfo?, restarting: Boolean) { super.onStartInputView(info, restarting) Log.d(TAG, "onStartInputView") inputConnection = currentInputConnection } override fun onCreateInputView(): View { Log.d(TAG, "onCreateInputView") keyboardView = layoutInflater.inflate(R.layout.keyboard_view, null) // Initialize Flutter engine first initializeFlutterEngine() setupKeyboard(keyboardView) setupActionButtons(keyboardView) setupResultButtons() showKeyboardState() return keyboardView } private fun showLoadingState() { Log.d(TAG, "Showing loading state") runOnUiThread { keyboardView.findViewById<View>(R.id.loading_container)?.visibility = View.VISIBLE keyboardView.findViewById<View>(R.id.layout_letters)?.visibility = View.GONE keyboardView.findViewById<View>(R.id.result_container)?.visibility = View.GONE } } private fun showKeyboardState() { Log.d(TAG, "Showing keyboard state") runOnUiThread { keyboardView.findViewById<View>(R.id.loading_container)?.visibility = View.GONE keyboardView.findViewById<View>(R.id.layout_letters)?.visibility = View.VISIBLE keyboardView.findViewById<View>(R.id.result_container)?.visibility = View.GONE } } private fun showResultState(text: String) { Log.d(TAG, "Showing result state: ${text.take(50)}...") runOnUiThread { keyboardView.findViewById<TextView>(R.id.result_text)?.text = text keyboardView.findViewById<View>(R.id.loading_container)?.visibility = View.GONE keyboardView.findViewById<View>(R.id.layout_letters)?.visibility = View.GONE keyboardView.findViewById<View>(R.id.result_container)?.visibility = View.VISIBLE } } private fun ensureInputConnection(): Boolean { if (inputConnection == null) { inputConnection = currentInputConnection } return inputConnection != null } private fun setupKeyboard(view: View) { // Setup letter keys letterKeys.forEach { key -> val id = resources.getIdentifier("key_${key.lowercase()}", "id", packageName) val button = view.findViewById<Button>(id) ?: return@forEach attachKeyTouch(button) { if (!ensureInputConnection()) return@attachKeyTouch val text = if (isShiftOn || isCapsLockOn) key else key.lowercase() inputConnection?.commitText(text, 1) if (isShiftOn && !isCapsLockOn) { isShiftOn = false updateKeyLabels(view) } } } // Setup number keys (0-9) for (i in 0..9) { view.findViewById<Button>(resources.getIdentifier("key_$i", "id", packageName))?.let { button -> attachKeyTouch(button) { if (!ensureInputConnection()) return@attachKeyTouch inputConnection?.commitText(i.toString(), 1) } } } // Setup special keys view.findViewById<Button>(R.id.key_space)?.let { attachKeyTouch(it) { if (!ensureInputConnection()) return@attachKeyTouch inputConnection?.commitText(" ", 1) } } view.findViewById<Button>(R.id.key_backspace)?.let { attachKeyTouch(it) { if (!ensureInputConnection()) return@attachKeyTouch inputConnection?.deleteSurroundingText(1, 0) } } view.findViewById<Button>(R.id.key_enter)?.let { attachKeyTouch(it) { if (!ensureInputConnection()) return@attachKeyTouch inputConnection?.sendKeyEvent( KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER) ) } } view.findViewById<Button>(R.id.key_shift)?.let { attachKeyTouch(it) { isShiftOn = !isShiftOn updateKeyLabels(view) } } view.findViewById<Button>(R.id.key_symbols)?.let { attachKeyTouch(it) { if (!ensureInputConnection()) return@attachKeyTouch inputConnection?.commitText(",", 1) } } updateKeyLabels(view) } // ================= ACTION BUTTONS ================= private fun setupActionButtons(root: View) { fun callFlutter(feature: String) { Log.d(TAG, "callFlutter called with feature: $feature") if (!ensureInputConnection()) { Log.e(TAG, "No input connection") return } val selectedText = inputConnection?.getSelectedText(0)?.toString() Log.d(TAG, "Selected text: $selectedText") val extractedText = inputConnection?.getExtractedText(ExtractedTextRequest(), 0)?.text?.toString() Log.d(TAG, "Extracted text: ${extractedText?.take(50)}...") val text = selectedText ?: extractedText ?: run { Log.e(TAG, "No text available") return } Log.d(TAG, "Calling API with text length: ${text.length}") showLoadingState() if (!isFlutterReady) { Log.e(TAG, "Flutter not ready — queueing call") pendingCalls.add(Pair(text, feature)) return } callFlutterInternal(text, feature) } root.findViewById<Button>(R.id.btn_check_grammar)?.setOnClickListener { Log.d(TAG, "Check grammar clicked") callFlutter("fix-grammar") } root.findViewById<Button>(R.id.btn_rephrase)?.setOnClickListener { Log.d(TAG, "Rephrase clicked") callFlutter("change-tone") } root.findViewById<Button>(R.id.btn_summarize)?.setOnClickListener { Log.d(TAG, "Summarize clicked") callFlutter("summarise") } root.findViewById<Button>(R.id.btn_ai_reply)?.setOnClickListener { Log.d(TAG, "AI Reply clicked") callFlutter("ai-reply") } root.findViewById<Button>(R.id.btn_email_gen)?.setOnClickListener { Log.d(TAG, "Email Gen clicked") callFlutter("email-generator") } root.findViewById<Button>(R.id.btn_prompt_gen)?.setOnClickListener { Log.d(TAG, "Prompt Gen clicked") callFlutter("prompt-generator") } } // ================= RESULT ================= private fun setupResultButtons() { keyboardView.findViewById<Button>(R.id.btn_apply)?.setOnClickListener { if (!ensureInputConnection()) return@setOnClickListener val result = keyboardView.findViewById<TextView>(R.id.result_text).text.toString() inputConnection?.commitText(result, 1) showKeyboardState() } keyboardView.findViewById<Button>(R.id.btn_close)?.setOnClickListener { showKeyboardState() } } // ================= TOUCH ================= private fun attachKeyTouch(view: View, onPress: () -> Unit) { view.setOnTouchListener { _, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { view.isPressed = true onPress() true } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { view.isPressed = false true } else -> false } } } private fun updateKeyLabels(view: View) { letterKeys.forEach { letter -> val id = resources.getIdentifier("key_${letter.lowercase()}", "id", packageName) view.findViewById<Button>(id)?.text = if (isShiftOn || isCapsLockOn) letter else letter.lowercase() } } private fun initializeFlutterEngine() { Log.d(TAG, "Initializing Flutter engine") val flutterLoader = FlutterInjector.instance().flutterLoader() if (!flutterLoader.initialized()) { flutterLoader.startInitialization(applicationContext) flutterLoader.ensureInitializationComplete(applicationContext, null) } flutterEngine = FlutterEngine(applicationContext) // ✅ REQUIRED for InputMethodService GeneratedPluginRegistrant.registerWith(flutterEngine) // ✅ ASSIGN to class field (not local) methodChannel = MethodChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL ) methodChannel.setMethodCallHandler { call, result -> when (call.method) { "flutterReady" -> { Log.d(TAG, "Flutter READY received") isFlutterReady = true pendingCalls.forEach { callFlutterInternal(it.first, it.second) } pendingCalls.clear() result.success(null) } else -> result.notImplemented() } } val entrypoint = DartExecutor.DartEntrypoint( flutterLoader.findAppBundlePath(), "keyboardToolbarMain" ) flutterEngine.dartExecutor.executeDartEntrypoint(entrypoint) val flutterView = FlutterView(this) flutterView.layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, (52 * resources.displayMetrics.density).toInt() ) flutterView.attachToFlutterEngine(flutterEngine) keyboardView .findViewById<FrameLayout>(R.id.flutter_toolbar_container) ?.addView(flutterView) } private fun callFlutterInternal(text: String, feature: String) { Log.d(TAG, "callFlutterInternal: $feature, text length: ${text.length}") Handler(Looper.getMainLooper()).post { try { methodChannel.invokeMethod( "callApi", mapOf( "text" to text, "feature" to feature ), object : MethodChannel.Result { override fun success(result: Any?) { Log.d( TAG, "API call succeeded, result type: ${result?.javaClass?.simpleName}" ) val resultText = result as? String ?: "No result received" Log.d(TAG, "Result text: ${resultText.take(100)}...") showResultState(resultText) } override fun error( code: String, message: String?, details: Any? ) { Log.e(TAG, "API error: $code - $message") showResultState("Error: $message") } override fun notImplemented() { Log.e(TAG, "Method not implemented") showResultState("Feature not available") } } ) } catch (e: Exception) { Log.e(TAG, "Exception calling Flutter", e) showResultState("Error: ${e.message}") } } } private fun runOnUiThread(action: () -> Unit) { Handler(Looper.getMainLooper()).post(action) } override fun onEvaluateFullscreenMode(): Boolean = false override fun onDestroy() { Log.d(TAG, "onDestroy") flutterEngine.destroy() super.onDestroy() } }keyboard_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="#E6E6E6" android:paddingStart="6dp" android:paddingEnd="6dp" android:paddingTop="4dp" android:paddingBottom="24dp"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="40dp" android:scrollbars="none" android:fillViewport="true"> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:gravity="center_vertical" android:paddingHorizontal="8dp"> <Button android:id="@+id/btn_check_grammar" android:layout_width="wrap_content" android:layout_height="36dp" style="@style/ActionPill" android:text="Check Grammar" /> <Button android:id="@+id/btn_rephrase" android:layout_width="wrap_content" android:layout_height="36dp" style="@style/ActionPill" android:text="Rephrase" /> <Button android:id="@+id/btn_summarize" android:layout_width="wrap_content" android:layout_height="36dp" style="@style/ActionPill" android:text="Summarize" /> <Button android:id="@+id/btn_ai_reply" android:layout_width="wrap_content" android:layout_height="36dp" style="@style/ActionPill" android:text="AI Reply" /> <Button android:id="@+id/btn_email_gen" android:layout_width="wrap_content" android:layout_height="36dp" style="@style/ActionPill" android:text="Email Gen" /> <Button android:id="@+id/btn_prompt_gen" android:layout_width="wrap_content" android:layout_height="36dp" style="@style/ActionPill" android:text="Prompt Gen" /> </LinearLayout> </HorizontalScrollView> <!-- ================= FLUTTER TOOLBAR ================= --> <FrameLayout android:id="@+id/flutter_toolbar_container" android:layout_width="match_parent" android:layout_height="52dp" /> <!-- ================= KEYBOARD ================= --> <LinearLayout android:id="@+id/layout_letters" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout style="@style/KeyRow" android:layout_width="match_parent" android:layout_height="50dp"> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="1"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="2"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="3"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="4"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="5"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="6"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="7"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="8"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="9"/> <Button style="@style/KeyNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="0"/> </LinearLayout> <!-- First row --> <LinearLayout style="@style/KeyRow" android:layout_width="match_parent" android:layout_height="48dp"> <Button android:id="@+id/key_q" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="Q"/> <Button android:id="@+id/key_w" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="W"/> <Button android:id="@+id/key_e" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="E"/> <Button android:id="@+id/key_r" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="R"/> <Button android:id="@+id/key_t" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="T"/> <Button android:id="@+id/key_y" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="Y"/> <Button android:id="@+id/key_u" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="U"/> <Button android:id="@+id/key_i" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="I"/> <Button android:id="@+id/key_o" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="O"/> <Button android:id="@+id/key_p" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="P"/> </LinearLayout> <!-- Second row --> <LinearLayout style="@style/KeyRow" android:layout_width="match_parent" android:layout_height="48dp"> <Button android:id="@+id/key_a" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="A"/> <Button android:id="@+id/key_s" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="S"/> <Button android:id="@+id/key_d" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="D"/> <Button android:id="@+id/key_f" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="F"/> <Button android:id="@+id/key_g" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="G"/> <Button android:id="@+id/key_h" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="H"/> <Button android:id="@+id/key_j" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="J"/> <Button android:id="@+id/key_k" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="K"/> <Button android:id="@+id/key_l" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="L"/> </LinearLayout> <!-- Third row --> <LinearLayout style="@style/KeyRow" android:layout_width="match_parent" android:layout_height="48dp"> <Button android:id="@+id/key_shift" style="@style/KeySpecial" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1.4" android:text="↑"/> <Button android:id="@+id/key_z" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="Z"/> <Button android:id="@+id/key_x" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="X"/> <Button android:id="@+id/key_c" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="C"/> <Button android:id="@+id/key_v" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="V"/> <Button android:id="@+id/key_b" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="B"/> <Button android:id="@+id/key_n" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="N"/> <Button android:id="@+id/key_m" style="@style/Key" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="M"/> <Button android:id="@+id/key_backspace" style="@style/KeySpecial" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1.4" android:text="⌫"/> </LinearLayout> <!-- Bottom row with space and enter --> <LinearLayout style="@style/KeyRowBottom" android:layout_width="match_parent" android:layout_height="44dp"> <Button android:id="@+id/key_symbols" style="@style/KeyBottomSpecial" android:text="!#1" /> <Button android:id="@+id/key_space" style="@style/KeyWideBottom" android:text="space" /> <Button android:id="@+id/key_enter" style="@style/KeyBottomSpecial" android:text="↵" /> </LinearLayout> </LinearLayout> <!-- ================= LOADING ================= --> <LinearLayout android:id="@+id/loading_container" android:layout_width="match_parent" android:layout_height="120dp" android:orientation="vertical" android:gravity="center" android:visibility="gone" android:background="#FFFFFF"> <ProgressBar android:layout_width="32dp" android:layout_height="32dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Rewriting…" android:textSize="14sp" android:textColor="#000000" android:paddingTop="8dp" /> </LinearLayout> <!-- ================= RESULT ================= --> <LinearLayout android:id="@+id/result_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="gone" android:background="#F5F5F5" android:padding="12dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Result:" android:textStyle="bold" android:textSize="16sp" android:textColor="#000000" /> <TextView android:id="@+id/result_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#000000" android:paddingVertical="8dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="end" android:orientation="horizontal"> <Button android:id="@+id/btn_apply" android:layout_width="wrap_content" android:layout_height="36dp" android:text="Apply" /> <Button android:id="@+id/btn_close" android:layout_width="36dp" android:layout_height="36dp" android:text="✕" /> </LinearLayout> </LinearLayout> </LinearLayout>keyboard_toolbar.dart
@pragma('vm:entry-point') void keyboardToolbarMain() { WidgetsFlutterBinding.ensureInitialized(); const channel = MethodChannel('YOUR_CHANNEL_NAME'); channel.invokeMethod('flutterReady'); runApp(const KeyboardToolbarApp()); }keyboard_toolbar_app.dart
class KeyboardToolbarApp extends StatefulWidget { const KeyboardToolbarApp({super.key}); @override State<KeyboardToolbarApp> createState() => _KeyboardToolbarAppState(); } class _KeyboardToolbarAppState extends State<KeyboardToolbarApp> { static const MethodChannel _channel = MethodChannel('keyboard_api'); @override void initState() { super.initState(); _channel.setMethodCallHandler(_handleMethodCall); // Notify Kotlin that Flutter is ready WidgetsBinding.instance.addPostFrameCallback((_) async { print("🟢 FLUTTER READY SENT"); await _channel.invokeMethod('flutterReady'); }); } Future<dynamic> _handleMethodCall(MethodCall call) async { print("🔥 METHOD RECEIVED FROM KOTLIN: ${call.method}"); try { if (call.method == "callApi") { final String text = call.arguments['text']; final String feature = call.arguments['feature']; Log.d("Flutter", "Received API call: $feature - $text"); final result = await callKeyboardApi(text, feature); Log.d("Flutter", "API result: $result"); return result; // This is sent back to Kotlin } } catch (e) { Log.e("Flutter", "Error in handleMethodCall: $e"); return "Error: $e"; } return null; } @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: SizedBox.shrink(), ); } } Future<String> callKeyboardApi(String text, String feature) async { try { Log.d("Flutter", "Calling keyboard API: $feature"); late String url; if (feature == "change-tone") { url = ApiEndpoints.changeToneRephrase(feature, "professional"); } else { url = ApiEndpoints.rePhrase(feature); } Log.d("Flutter", "URL: $url, Text: $text"); final request = NetworkRequest( url: url, method: NetworkRequestType.POST, params: {"text": text}, callback: (data, code, message) {}, ); final response = await AppNetworking.instance.callRequestAsync(request); Log.d("Flutter", "Raw response: $response"); if (response == null || response.isEmpty) { return "No response from API"; } final parsed = RephraseResponse.fromJson(response); return parsed.data ?? "No data received"; } catch (e) { Log.e("Flutter", "Error in callKeyboardApi: $e"); return "Error: $e"; } } // Add this simple logging class class Log { static void d(String tag, String message) { print("[$tag] $message"); } static void e(String tag, String message) { print("ERROR [$tag] $message"); } }