Reputation: 7092
In a barebones Android application, I want to call a method in the MainActivity instance, from an asynchronous callback in the instance of another class. Why does Java force me to call a static method in the class, rather than a non-static method in the instance itself?
My app has a MainActivity class and a TextToSpeech (TTS) class. From the main activity, I instantiate the class, passing a pointer to the MainActivity instance. Instantiation of the TTS engine is an asynchronous operation. I cannot interact with the TTS instance until it has triggered an onInit() method.
Below is code that works. However, I had imagined that I would be able to call a non-static method in the MainActivity instance, and this appears not to be possible. The code below uses a static call to the MainActivity class itself, so no instance variables are available.
Here are the changes I made to a basic Hello World application in Android Studio.
//MainActivity.java
package com.example.callback;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends ActionBarActivity {
private static TTS tts; // apparently this has to be static
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onResume() {
super.onResume();
tts = new TTS();
Log.d("onResume", this.toString());
// D/Main﹕ com.example.callback.MainActivity@4a014e50
tts.init(this);
}
// Apparently this method has to be static
public static void ttsReady() {
tts.speakText("Hello world");
}
}
Custom class.
// TTS.java
package com.example.callback;
import android.app.Activity;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import java.util.HashMap;
import java.util.Locale;
public class TTS implements TextToSpeech.OnInitListener {
private TextToSpeech tts;
private Activity activity;
public void init(Activity currentActivity) {
activity = currentActivity;
Context context = activity.getApplicationContext();
tts = new android.speech.tts.TextToSpeech(context, this);
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.UK);
Log.d("onInit", activity.toString());
// D/onInit﹕ com.example.callback.MainActivity@4a014e50
// activity.ttsReady();
// The line above does not compile. ttsReady() is not recognized as a
// method of activity, regardless of whether the tts variable and the
// ttsReady() method in the MainActivity class are made static or not,
// Making the activity variable static here has no effect either.
// The commented line below throws Error:(31, 35)
// error: non-static method toString() cannot be referenced from a static context
// So presumably MainActivity is a static variable.
// Log.d("onInit", MainActivity.toString())
// This works, if the tts variable and the ttsReady() method in the
// MainActivity class are made static. Is there a non-static alternative?
MainActivity.ttsReady();
}
}
// Deprecated signature for speak() used for compatibility with API 20
// and earlier
public void speakText(String toSpeak) {
int mode = android.speech.tts.TextToSpeech.QUEUE_FLUSH;
// Object hashMap = null; // Causes a "no suitable method error".
// How is HashMap null not the same as Object null? Using just plain null
// instead of hashMap also works with no problems.
HashMap hashMap = null;
tts.speak(toSpeak, mode, hashMap);
}
}
Upvotes: 2
Views: 4537
Reputation: 13636
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.UK);
Log.d("onInit", activity.toString());
((MainActivity)activity).ttsReady();
}
}
You are not referring to the instance of MainActivity
when you say MainActivity.ttsReady()
. That is why you can only access static methods. You need to make a call to the instance itself which you have stored in the activity
variable.
Edit: For a complete solution based on what you're trying to accomplish, I would probably set it up this way:
// TTSActivity.java
public abstract class TTSActivity extends Activity implements TextToSpeech.OnInitListener {
private TextToSpeech tts;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tts = new TextToSpeech(this, this);
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.UK);
ttsReady();
}
}
public void speakText(String toSpeak) {
int mode = android.speech.tts.TextToSpeech.QUEUE_FLUSH;
tts.speak(toSpeak, mode, null);
}
protected TextToSpeech getTts() {
return tts;
}
protected abstract void ttsReady();
}
then MainActivity
becomes:
// MainActivity.java
public class MainActivity extends TTSActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void ttsReady() {
speakText("Hello world");
}
}
Any Activity
you want to use TTS in, all you have to do is extend TTSActivity
and implements ttsReady()
.
As you can see, this is much more concise and easier to understand. However, your exact implementation will of course be dependent upon all the requirements you have in your particular application.
Upvotes: 3
Reputation: 7092
@zaventh's answer helped me to realize that a Java instance needs to be cast to the right class or interface for its methods to be found in the inheritance hierarchy. I have now rewritten my barebones project to include an interface that declares the ttsReady() method.
Here is the solution that allows me to access the methods of the MainActivity instance in a generic way.
TTSUser Interface
package com.example.callback;
interface TTSUser {
void ttsReady();
}
MainActivity
package com.example.callback;
import android.app.Activity;
import android.os.Bundle;
// TTUser Interface ensures the existence of the ttsReady() method in every instance
public class MainActivity extends Activity implements TTSUser {
private TTS tts; // non-static
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onResume() {
super.onResume();
tts = new TTS();
tts.init(this);
}
public void ttsReady() { // non-static
tts.speakText("Hello world");
}
}
TTS Class
package com.example.callback;
import android.app.Activity;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import java.util.Locale;
public class TTS implements TextToSpeech.OnInitListener {
private TextToSpeech tts;
private TTSUser activity;
// Use the TTSUser interface in the signature
public void init(TTSUser activity) {
this.activity = activity;
// Cast to generic Activity, to access .getApplicationContext()
Context context = ((Activity) activity).getApplicationContext();
tts = new android.speech.tts.TextToSpeech(context, this);
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.UK);
activity.ttsReady(); // accessible through the TTSUser interfaceg
}
}
// Deprecated signature for speak() used for compatibility with API 20
// and earlier
public void speakText(String toSpeak) {
int mode = android.speech.tts.TextToSpeech.QUEUE_FLUSH;
tts.speak(toSpeak, mode, null);
}
}
Upvotes: 0