Интеграция API не работает на пользовательской клавиатуре в Flutter AndroidAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Интеграция API не работает на пользовательской клавиатуре в Flutter Android

Сообщение Anonymous »

Это большая проблема, с которой я сталкиваюсь, и пытался исследовать ее повсюду, но у меня это не работает, пожалуйста, если кто-нибудь может решить ее для меня

Я просто пытался вызвать API на пользовательской клавиатуре, например, когда выделен текст и нажмите любую кнопку для примера. Проверьте грамматику, затем API должен вызвать в соответствии с моим кодом и показать результат на клавиатуре, где отображаются буквы, там будет показан ответ, когда API вызывается, он показывает бесконечную загрузку, и ничего не печатает журналы, и ничего не отображается, я даже не знаю, что является точным проблема

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()

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(R.id.loading_container)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.GONE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE
}
}

private fun showKeyboardState() {
Log.d(TAG, "Showing keyboard state")
runOnUiThread {
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE
}
}

private fun showResultState(text: String) {
Log.d(TAG, "Showing result state: ${text.take(50)}...")
runOnUiThread {
keyboardView.findViewById(R.id.result_text)?.text = text
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.GONE
keyboardView.findViewById(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(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(resources.getIdentifier("key_$i", "id", packageName))?.let { button ->
attachKeyTouch(button) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.commitText(i.toString(), 1)
}
}
}

// Setup special keys
view.findViewById(R.id.key_space)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.commitText(" ", 1)
}
}

view.findViewById(R.id.key_backspace)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.deleteSurroundingText(1, 0)
}
}

view.findViewById(R.id.key_enter)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.sendKeyEvent(
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)
)
}
}

view.findViewById(R.id.key_shift)?.let {
attachKeyTouch(it) {
isShiftOn = !isShiftOn
updateKeyLabels(view)
}
}

view.findViewById(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(R.id.btn_check_grammar)?.setOnClickListener {
Log.d(TAG, "Check grammar clicked")
callFlutter("fix-grammar")
}
root.findViewById(R.id.btn_rephrase)?.setOnClickListener {
Log.d(TAG, "Rephrase clicked")
callFlutter("change-tone")
}
root.findViewById(R.id.btn_summarize)?.setOnClickListener {
Log.d(TAG, "Summarize clicked")
callFlutter("summarise")
}
root.findViewById(R.id.btn_ai_reply)?.setOnClickListener {
Log.d(TAG, "AI Reply clicked")
callFlutter("ai-reply")
}
root.findViewById(R.id.btn_email_gen)?.setOnClickListener {
Log.d(TAG, "Email Gen clicked")
callFlutter("email-generator")
}
root.findViewById(R.id.btn_prompt_gen)?.setOnClickListener {
Log.d(TAG, "Prompt Gen clicked")
callFlutter("prompt-generator")
}
}

// ================= RESULT =================

private fun setupResultButtons() {
keyboardView.findViewById(R.id.btn_apply)?.setOnClickListener {
if (!ensureInputConnection()) return@setOnClickListener
val result = keyboardView.findViewById(R.id.result_text).text.toString()
inputConnection?.commitText(result, 1)
showKeyboardState()
}

keyboardView.findViewById(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(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(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
























































































































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 createState() => _KeyboardToolbarAppState();
}

class _KeyboardToolbarAppState extends State {
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 _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 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");
}
}


Подробнее здесь: https://stackoverflow.com/questions/798 ... er-android
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»