Talk with Gemini – Android App
Steps:
- Setup Gemini API Key
- Develop the code in Android Studio
I hope you like this video. For any questions, suggestions or appreciation please contact us at: https://programmerworld.co/contact/ or email at: programmerworld1990@gmail.com
Details:
package com.programmerworld.talktogemini;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.google.ai.client.generativeai.GenerativeModel;
import com.google.ai.client.generativeai.java.GenerativeModelFutures;
import com.google.ai.client.generativeai.type.Content;
import com.google.ai.client.generativeai.type.GenerateContentResponse;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
private static final String PREFS_NAME = "AIVoiceAssistantPrefs";
private static final String CONVERSATION_KEY = "ConversationHistory";
private TextToSpeech textToSpeech;
private ListView conversationListView;
private Button controlButton;
private GenerativeModelFutures generativeModel;
private ArrayList<String> conversationHistory;
private ArrayAdapter<String> conversationAdapter;
private SharedPreferences prefs;
private boolean isListening = false;
private SpeechRecognizer speechRecognizer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
conversationListView = findViewById(R.id.conversationListView);
controlButton = findViewById(R.id.controlButton);
// Initialize SharedPreferences
prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
// Load conversation history from SharedPreferences
String savedConversation = prefs.getString(CONVERSATION_KEY, "");
conversationHistory = new ArrayList<>();
if (!savedConversation.isEmpty()) {
conversationHistory.addAll(Arrays.asList(savedConversation.split("\n")));
}
// Set up ListView with ArrayAdapter
conversationAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, conversationHistory);
conversationListView.setAdapter(conversationAdapter);
// Initialize TextToSpeech
textToSpeech = new TextToSpeech(this, status -> {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.US);
// Set UtteranceProgressListener instead of OnUtteranceCompletedListener
textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
// Called when utterance starts (optional)
}
@Override
public void onDone(String utteranceId) {
// Called when utterance is complete
runOnUiThread(() -> {
if (isListening) {
startListening(); // Restart listening after speech is complete
}
});
}
@Override
public void onError(String s) {
// Called if an error occurs during utterance
runOnUiThread(() -> {
addToConversation("AI: Error in text-to-speech: " + s);
});
}
@Override
public void onError(String utteranceId, int errorCode) {
// Called if an error occurs during utterance
runOnUiThread(() -> {
addToConversation("AI: Error in text-to-speech: " + getTtsErrorText(errorCode));
});
}
});
speak("Hello, I'm your AI assistant. Press the button to start listening.");
runOnUiThread(() -> {
addToConversation("AI: Hello, I'm your AI assistant. Press the button to start listening.");
});
}
});
// Initialize Gemini 1.5 Flash model
GenerativeModel gm = new GenerativeModel(
"gemini-1.5-flash",
"AIzaSyAzxxxJp7S4DOToSR6nGL_6o7q9Y9W_IYY" // Replace with BuildConfig.API_KEY if using Gradle
);
generativeModel = GenerativeModelFutures.from(gm);
// Initialize SpeechRecognizer
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
speechRecognizer.setRecognitionListener(new RecognitionListener() {
@Override
public void onReadyForSpeech(Bundle params) {
// Not used, but required by interface
}
@Override
public void onBeginningOfSpeech() {
// Not used, but required by interface
}
@Override
public void onRmsChanged(float rmsdB) {
// Not used, but required by interface
}
@Override
public void onBufferReceived(byte[] buffer) {
// Not used, but required by interface
}
@Override
public void onEndOfSpeech() {
// Not used, but required by interface
}
@Override
public void onError(int error) {
Toast.makeText(getApplicationContext(),
"Error in speech recognition: ",
Toast.LENGTH_SHORT).show();
runOnUiThread(() -> {
addToConversation("AI: Stopped listening. Press the button to start again.");
if (isListening) {
startListening(); // Retry on error if still listening
}
});
}
@Override
public void onResults(Bundle results) {
ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null && !matches.isEmpty() && isListening) {
String spokenText = matches.get(0);
addToConversation("You: " + spokenText);
processCommandWithGemini(spokenText);
}
}
@Override
public void onPartialResults(Bundle partialResults) {
// Not used, but required by interface
}
@Override
public void onEvent(int eventType, Bundle params) {
// Not used, but required by interface
}
});
// Request permissions
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CALL_PHONE},
REQUEST_RECORD_AUDIO_PERMISSION);
// Set initial button state to "Listen"
controlButton.setText("Listen");
controlButton.setOnClickListener(v -> toggleListening());
}
private void toggleListening() {
if (isListening) {
// Stop listening
isListening = false;
controlButton.setText("Listen");
if (speechRecognizer != null) {
speechRecognizer.stopListening();
}
speak("Stopped listening. Press the button to start again.");
} else {
// Start listening
isListening = true;
controlButton.setText("Stop");
startListening();
}
}
private void startListening() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED) {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
speechRecognizer.startListening(intent);
} else {
addToConversation("AI: Audio permission not granted");
Toast.makeText(this, "Audio permission not granted", Toast.LENGTH_SHORT).show();
isListening = false;
controlButton.setText("Listen");
}
}
private void processCommandWithGemini(String command) {
Content content = new Content.Builder()
.addText("Act as a voice assistant. Here’s the conversation history:\n" +
String.join("\n", conversationHistory) + "\nProcess this command: " + command)
.build();
ListenableFuture<GenerateContentResponse> future = generativeModel.generateContent(content);
Futures.addCallback(future, new FutureCallback<GenerateContentResponse>() {
@Override
public void onSuccess(GenerateContentResponse result) {
String response = result.getText();
runOnUiThread(() -> {
addToConversation("AI: " + response);
speak(response);
});
}
@Override
public void onFailure(Throwable t) {
runOnUiThread(() -> {
String errorMsg = "Error processing command: " + t.getMessage();
addToConversation("AI: " + errorMsg);
speak(errorMsg);
t.printStackTrace();
});
}
}, Executors.newSingleThreadExecutor());
}
private void addToConversation(String message) {
conversationHistory.add(message);
conversationAdapter.notifyDataSetChanged();
saveConversationHistory();
conversationListView.setSelection(conversationAdapter.getCount() - 1);
}
private void saveConversationHistory() {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(CONVERSATION_KEY, String.join("\n", conversationHistory));
editor.apply();
}
private void speak(String text) {
HashMap<String, String> params = new HashMap<>();
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "UniqueID");
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params);
}
private String getTtsErrorText(int errorCode) {
switch (errorCode) {
case TextToSpeech.ERROR:
return "General text-to-speech error";
case TextToSpeech.ERROR_NETWORK:
return "Network error in text-to-speech";
case TextToSpeech.ERROR_NETWORK_TIMEOUT:
return "Network timeout in text-to-speech";
case TextToSpeech.ERROR_INVALID_REQUEST:
return "Invalid request in text-to-speech";
case TextToSpeech.ERROR_SERVICE:
return "Text-to-speech service error";
case TextToSpeech.ERROR_OUTPUT:
return "Output error in text-to-speech";
case TextToSpeech.ERROR_SYNTHESIS:
return "Synthesis error in text-to-speech";
default:
return "Unknown text-to-speech error";
}
}
@Override
protected void onDestroy() {
if (textToSpeech != null) {
textToSpeech.stop();
textToSpeech.shutdown();
}
if (speechRecognizer != null) {
speechRecognizer.destroy();
}
isListening = false; // Stop listening when app is closed
super.onDestroy();
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TalkToGemini"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
plugins {
alias(libs.plugins.android.application)
}
android {
namespace = "com.programmerworld.talktogemini"
compileSdk = 35
defaultConfig {
applicationId = "com.programmerworld.talktogemini"
minSdk = 33
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation("com.google.ai.client.generativeai:generativeai:0.9.0")
implementation("com.google.guava:guava:33.3.1-android") // Use the latest Android-compatible version
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<ListView
android:id="@+id/conversationListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_above="@id/controlButton"
android:layout_alignParentTop="true"
android:transcriptMode="alwaysScroll" />
<Button
android:id="@+id/controlButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Listen"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"/>
</RelativeLayout>
Screenshots:

