Open another activity when dialog's button is pressed? - java

I'd like to successfully run the code below (belonging to this library) when any of the buttons from this Rating Dialog are pressed. How can I do it?
public void goToPickerActivity() {
Intent intent = new com.sucho.placepicker.PlacePicker.IntentBuilder()
.onlyCoordinates(true) //Get only Coordinates from Place Picker
.showLatLong(true)
.setLatLong(40.71274, -74.005974) // Initial Latitude and Longitude the Map will load into (NYC coordinates)
.setMapZoom(2.5f) // Map Zoom Level. Default: 14.0
.build(this);
startActivityForResult(intent, Constants.PLACE_PICKER_REQUEST);
}
Edit: I want to execute goToPickerActivity() first and then execute the library's intended click code. For example, when Rate App button is clicked:
Step 1 is: execute goToPickerActivity().
Once that's complete, Step 2 is: run the usual Rate App clicked code.

You can try to add a OnRatingDialogListener using setRateButtonText, setRateLaterButtonText or setNeverRateButtonText methods of AppRatingDialog.Builder class:
final AppRatingDialog appRatingDialog = new AppRatingDialog.Builder(this)
// ... call another setup methods if need
.setRateButtonText("Rate App", new OnRatingDialogListener() {
#Override
public void onClick() {
goToPickerActivity();
}
})
.setRateLaterButtonText("Rate App Later", new OnRatingDialogListener() {
#Override
public void onClick() {
goToPickerActivity();
}
})
.setNeverRateButtonText("Never", new OnRatingDialogListener() {
#Override
public void onClick() {
goToPickerActivity();
}
})
.build();
appRatingDialog.show();
Using lambda it will look like this:
final AppRatingDialog appRatingDialog = new AppRatingDialog.Builder(this)
// ... call another setup methods if need
.setRateButtonText("Rate App", () -> goToPickerActivity())
.setRateLaterButtonText("Rate App Later", () -> goToPickerActivity())
.setNeverRateButtonText("Never", () -> goToPickerActivity())
.build();
appRatingDialog.show();
EDIT 1:
Method openPlayStore:
private void openPlayStore() {
final String storeLink = "market://details?id=" + context.getPackageName();
final Uri marketUri = Uri.parse(storeLink);
try {
mContext.startActivity(new Intent(Intent.ACTION_VIEW, marketUri));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(mContext, "Couldn't find PlayStore on this device", Toast.LENGTH_SHORT).show();
}
}
If you can edit the library try to make openPlayStore method public, save reference to AppRatingDialog instanse to some variable, e.g. appRatingDialog and call appRatingDialog.openPlayStore().
EDIT 2:
To have different listeners for buttons we need to add new listeners to AppRatingDialog and handle them:
public class AppRatingDialog extends AppCompatDialog implements View.OnClickListener {
//...
private OnRatingDialogListener onRatingDialogListener;
private OnRatingDialogListener onRateLaterDialogListener;
private OnRatingDialogListener onRateNeverDialogListener;
//...
#Override
public void onClick(View v) {
if (v.getId() == R.id.dialog_rating_button_never_rate) {
savedNeverShow();
dismiss();
if (onRateNeverDialogListener != null) {
onRateNeverDialogListener.onClick();
}
} else if (v.getId() == R.id.dialog_rating_button_rate_later) {
dismiss();
incrementLaunchCount(true);
if (onRateLaterDialogListener != null) {
onRateLaterDialogListener.onClick();
}
} else if (v.getId() == R.id.dialog_rating_button_rate) {
savedNeverShow();
dismiss();
if (onRatingDialogListener != null) {
onRatingDialogListener.onClick();
} else {
openPlayStore();
}
}
}
// ...
public static class Builder {
//...
#NonNull
public Builder setRateLaterButtonText(String rateLaterButtonText, #Nullable OnRatingDialogListener onRateLaterClickListener) {
appRatingDialog.mRateLaterButtonText = rateLaterButtonText;
appRatingDialog.onRateLaterDialogListener = onRateLaterClickListener;
return this;
}
#NonNull
public Builder setNeverRateButtonText(String neverRateButtonText, #Nullable OnRatingDialogListener onNeverRateClickListener) {
appRatingDialog.mNeverRateButtonText = neverRateButtonText;
appRatingDialog.onRateNeverDialogListener = onNeverRateClickListener;
return this;
}
// ...
}
}
EDIT 3:
To open Play Store and notify a listener simultaneously:
public class AppRatingDialog extends AppCompatDialog implements View.OnClickListener {
//...
private OnRatingDialogListener onRatingDialogListener;
private OnRatingDialogListener onRateLaterDialogListener;
private OnRatingDialogListener onRateNeverDialogListener;
//...
#Override
public void onClick(View v) {
if (v.getId() == R.id.dialog_rating_button_never_rate) {
savedNeverShow();
dismiss();
if (onRateNeverDialogListener != null) {
onRateNeverDialogListener.onClick();
}
} else if (v.getId() == R.id.dialog_rating_button_rate_later) {
dismiss();
incrementLaunchCount(true);
if (onRateLaterDialogListener != null) {
onRateLaterDialogListener.onClick();
}
} else if (v.getId() == R.id.dialog_rating_button_rate) {
savedNeverShow();
dismiss();
openPlayStore();
if (onRatingDialogListener != null) {
onRatingDialogListener.onClick();
}
}
}
// ...
}

Please follow these steps:
In the library module look for class called AppRatingDialog.java add YOURACTIVITY.goToPickerActivity(**boolean**); to each view of onClick (at line 171) method like this one:
#Override
public void onClick(View v) {
if (v.getId() == R.id.dialog_rating_button_never_rate) {
YOURACTIVITY.goToPickerActivity(false);
savedNeverShow();
dismiss();
if (onRatingDialogListener != null) {
onRatingDialogListener.onClick();
}
} else if (v.getId() == R.id.dialog_rating_button_rate_later) {
YOURACTIVITY.goToPickerActivity(false);
dismiss();
incrementLaunchCount(true);
if (onRatingDialogListener != null) {
onRatingDialogListener.onClick();
}
} else if (v.getId() == R.id.dialog_rating_button_rate) {
YOURACTIVITY.goToPickerActivity(true);
savedNeverShow();
dismiss();
if (onRatingDialogListener != null) {
onRatingDialogListener.onClick();
}
}
}
In your activity modify your method goToPickerActivity() to be something like this:
private static void goToPickerActivity(boolean isRate) {
Intent intent = new com.sucho.placepicker.PlacePicker.IntentBuilder()
.onlyCoordinates(true) //Get only Coordinates from Place Picker
.showLatLong(true)
.setLatLong(40.71274, -74.005974) // Initial Latitude and Longitude the Map will load into (NYC coordinates)
.setMapZoom(2.5f) // Map Zoom Level. Default: 14.0
.build(this);
if (isRate){
startActivityForResult(intent, Constants.PLACE_PICKER_REQUEST_RATE);
} else {
startActivityForResult(intent, Constants.PLACE_PICKER_REQUEST);
}
in method onActivityResult check the request code if it's equal to Constants.PLACE_PICKER_REQUEST_RATE then call method openStore():
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if (requestCode == Constants.PLACE_PICKER_REQUEST_RATE) {
//other code ...
openStore();
}
}
}
private void openStore() {
final Uri storeUri = Uri.parse(mStoreLink); // your app store listing link
try {
startActivity(new Intent(Intent.ACTION_VIEW, storeUri));
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
You can add handler to openStore() if you want to wait for certain time before go to store page:
Handler mHandler = new Handler();
mHandler.postDelayed(() -> {
openStore();
}, 1000); // wait for 1 second

You can simply write a class and extend from AppRatingDialog. then by overriding onClick method you can do what you want. then by calling super.onClick you'll run the library onClick method.
#Override
public void onClick(View v) {
if (v.getId() == R.id.dialog_rating_button_rate) {
goToPickerActivity();
}
super.onClick(v);
}

As you are able to edit the library code, to match up your requirement, first of all, change appratingdialog library code. Go to AppRatingDialog.java and inside onClick(View v) modify the rating button condition like below:
if (v.getId() == R.id.dialog_rating_button_rate) {
savedNeverShow();
dismiss();
if (onRatingDialogListener != null) {
onRatingDialogListener.onClick();
openPlayStore(); // Just added this extra line here, others are kept in place
} else {
openPlayStore();
}
}
Now, inside your app activity, create custom click listener for rating button using setRateButtonText like below:
AppRatingDialog appRatingDialog = new AppRatingDialog.Builder(MainActivity.this)
.setTriggerCount(3)
.setRepeatCount(4)
.setRateButtonText("Rate Now", clickListener)
.build();
appRatingDialog.show();
Finally, implement the click listener inside your app activity having the PlacePicker intent inside onClick like below:
OnRatingDialogListener clickListener = new OnRatingDialogListener() {
#Override
public void onClick() {
goToPickerActivity();
}
};

You actually seem to have this already answered in your original post.
startActivityForResult(intent, Constants.PLACE_PICKER_REQUEST);
public void goToPickerActivity() {
Intent intent = new com.sucho.placepicker.PlacePicker.IntentBuilder()
.onlyCoordinates(true) //Get only Coordinates from Place Picker
.showLatLong(true)
.setLatLong(40.71274, -74.005974) // Initial Latitude and Longitude the Map will load into (NYC coordinates)
.setMapZoom(2.5f) // Map Zoom Level. Default: 14.0
.build(this);
startActivityForResult(intent, Constants.PLACE_PICKER_REQUEST);
}
So override onActivityResult() from within there you can determine if the activity was successful and then do what you need like below
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (!(requestCode == 0 && resultCode == RESULT_OK)){
onBackPressed();
}else{
goToPickerActivity()
}
}

Try to update these following dependencies in your build.gradle project level:
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:$the_latest_version'

Related

Playing multiple sounds via multiple buttons from raw in background using service

I want to play 6 different sounds triggered by 6 different buttons in background, so that if the app is on background the sound keeps playing.
When one sound is already playing, pressing another button will stop it and play its own sound,
Tapping the same button 2K times it stops, 2K+1 times: starts again.. (K is a non-null integer)
All of the code is done and seems to be working correctly, except that the player stops after one and a half minute. (This is not because of low memory)
Can anyone please tell me what am I doing wrong?
public class PlayService extends Service {
private MediaPlayer player;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
player = new MediaPlayer();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
int btnId = intent.getExtras().getInt("ID");
Toast.makeText(this, "onStart service" + btnId, Toast.LENGTH_SHORT).show();
selectResId(btnId);
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "Service destroyed", Toast.LENGTH_SHORT).show();
if (player != null) {
player.stop();
player.release();
}
player = null;
}
#Override
public void onLowMemory() {
super.onLowMemory();
Toast.makeText(this, "Low mem", Toast.LENGTH_SHORT).show();
}
private void selectResId(int resId){
switch (resId){
case 1: playMediaFromResource(R.raw.number_one);
case 2: playMediaFromResource(R.raw.number_two);
case 3: playMediaFromResource(R.raw.number_three);
case 4: playMediaFromResource(R.raw.number_four);
case 5: playMediaFromResource(R.raw.number_five);
case 6: playMediaFromResource(R.raw.number_six);
default: break;
}
}
private void playMediaFromResource(int resId) {
Uri mediaPath = Uri.parse("android.resource://" + getPackageName() + "/" + resId);
try {
player.setDataSource(getApplicationContext(), mediaPath);
player.setLooping(true);
player.prepare();
player.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
And the MainActivity:
public class MainActivity extends AppCompatActivity {
private Button btnStart1;
private Button btnStart2;
private Button btnStart3;
private Button btnStart4;
private Button btnStart5;
private Button btnStart6;
private Intent intent;
private int previousID = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewsByIds();
setOnClickListeners();
}
private void findViewsByIds() {
btnStart1 = findViewById(R.id.btn_start_1);
btnStart2 = findViewById(R.id.btn_start_2);
btnStart3 = findViewById(R.id.btn_start_3);
btnStart4 = findViewById(R.id.btn_start_4);
btnStart5 = findViewById(R.id.btn_start_5);
btnStart6 = findViewById(R.id.btn_start_6);
}
private void setOnClickListeners() {
btnStart1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkIntentState(1);
}
});
btnStart2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkIntentState(2);
}
});
btnStart3.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkIntentState(3);
}
});
btnStart4.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkIntentState(4);
}
});
btnStart5.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkIntentState(5);
}
});
btnStart6.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkIntentState(6);
}
});
}
private void checkIntentState(int ID) {
if (intent == null) {
createNewIntent(ID);
} else {
stopService(intent);
intent = null;
if (ID != previousID) {
createNewIntent(ID);
}
}
}
private void createNewIntent(int ID) {
intent = new Intent(MainActivity.this, PlayService.class);
intent.putExtra("ID", ID);
startService(intent);
previousID = ID;
}
}
I want to answer to my own question just in case anyone else runs into the problem.
It turns out, that Android added some new features (restricted access to background resources for battery life improvement purposes since Oreo(i.e. Android 8.0+ || API level 26)).
As the documentation says:
"Apps that are running in the background now have limits on how freely they can access background services."
So, in this case we will need to use foreground services.

multiple onClickListener on a single Button

in my project, I have to use dialogs in different parts of the app and the downside is same code for creating and showing the Dialog used every time! So I decided to make a function for creating and showing a dialog and wherever I need I just call that,
the problem is, there are 2 buttons on my Dialog and before refactoring on the onClickListener I could easily use
dialog.dismiss();
// more codes ...
in setOnClickListener
but now with the incoming OnClickListener Object, I have no control over the dialog instance ...
this is the function I wrote
public static void warningAndErrorDialog(Activity activity, int titleResourceId, int iconResourceId, int contentResourceId
, HashMap<CustomDialogButton, View.OnClickListener> buttons) {
Typeface iranSansFont = setFont(activity, FontStyle.IRAN_SANS_REGULAR);
final Dialog dialog = new Dialog(activity);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog);
CustomFontTextView cftvTitle = (CustomFontTextView)
dialog.findViewById(R.id.txtViwDialogTitle);
if(activity.getString(titleResourceId) != null)
cftvTitle.setText(titleResourceId);
else cftvTitle.setText(" ");
CustomFontTextView cftvContent = (CustomFontTextView)
dialog.findViewById(R.id.txtViwDialogContent);
if(activity.getString(contentResourceId) != null)
cftvContent.setText(contentResourceId);
else cftvTitle.setText(" ");
ImageView imgViwDialogTitle = dialog.findViewById(R.id.imgViwDialogTitle);
imgViwDialogTitle.setImageResource(iconResourceId);
Button btnYes = null;
Button btnNo = null;
for (Map.Entry<CustomDialogButton, View.OnClickListener> button : buttons.entrySet())
switch (button.getKey()) {
case YES:
if (btnYes != null) break;
btnYes = dialog.findViewById(R.id.btnYes);
btnYes.setTypeface(iranSansFont);
if (button.getValue() != null)
btnYes.setOnClickListener(button.getValue());
break;
case NO:
if (btnNo != null) break;
btnNo = dialog.findViewById(R.id.btnNo);
if (button.getValue() != null) {
btnNo.setTypeface(iranSansFont);
btnNo.setOnClickListener(button.getValue());
} else
btnNo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
dialog.dismiss();
}
});
break;
case OK:
btnYes = dialog.findViewById(R.id.btnYes);
btnYes.setText(R.string.ok);
if (button.getValue() != null) btnYes.setOnClickListener(button.getValue());
else
btnYes.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
dialog.dismiss();
}
});
btnNo = dialog.findViewById(R.id.btnNo);
btnNo.setVisibility(View.INVISIBLE);
return;
}
dialog.show();
}
notice of case YES:
I set button setOnClickListener from an onClickListener I passed to the function.
so I have no control over its content. now How Can I add statement dialog.dismiss(); ?
the Only thing I can think of is finding a way to pass an array of OnClickListener to the setOnCLickListener method.
one for dismissing the dialog and another one for setting the actual job of the button...
ps: I tried dismissing the dialog using setOnTouchListener but as I expected, that did not work...: -?
so what should I do?
final edit :
someone suggested
btnYes.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
for (View.OnClickListener callback : callbacks)
if (callback != null)
callback.onClick(view);
}
});
(callbacks is a List of View.OnClickListener object) and thats the exact answer to "how to add multiple onClickListener on a single Button" question.
but #greenapps solution is the best solution that suits my need, thanks :)
so I share the final version of the code for whom the may concern :D :
public class CustomDialog {
private final Dialog dialog;
private Typeface font;
private CustomFontTextView cftvTitle;
private CustomFontTextView cftvContent;
private ImageView imgViwDialogTitle;
private Activity activity;
private Button btnYes;
private Button btnNo;
public CustomDialog(final Activity activity, int titleResourceId, int iconResourceId, int contentResourceId
, HashMap<CustomDialogButton, View.OnClickListener> buttons) {
font = Utility.setFont(activity, FontStyle.IRAN_SANS_REGULAR);
this.activity = activity;
dialog = new Dialog(this.activity);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog);
setTitleTextbyId(titleResourceId);
setContentTextById(contentResourceId);
setTitleIconById(iconResourceId);
setButtons(buttons);
}
public CustomDialog(Activity activity) {
font = Utility.setFont(activity, FontStyle.IRAN_SANS_REGULAR);
this.activity = activity;
dialog = new Dialog(this.activity);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog);
setTitleTextbyId(0);
setContentTextById(0);
setTitleIconById(0);
setButtons(null);
}
public void show() {
dialog.show();
}
public void setButtons(HashMap<CustomDialogButton, View.OnClickListener> buttons) {
for (final Map.Entry<CustomDialogButton, View.OnClickListener> button : buttons.entrySet())
switch (button.getKey()) {
case YES:
setButtonYes(button);
break;
case NO:
setButtonNo(button);
break;
case OK:
setButtonYes(button);
btnYes.setText(R.string.ok);
btnNo = dialog.findViewById(R.id.btnNo);
btnNo.setVisibility(View.INVISIBLE);
break;
}
}
#NonNull
private void setButtonNo(final Map.Entry<CustomDialogButton, View.OnClickListener> button) {
if (btnNo != null) return;
btnNo = dialog.findViewById(R.id.btnNo);
btnNo.setTypeface(font);
btnNo.setOnClickListener(button.getValue());
btnNo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
dialog.dismiss();
if(button.getValue() != null)
button.getValue().onClick(view);
}
});
}
#NonNull
private void setButtonYes(final Map.Entry<CustomDialogButton, View.OnClickListener> button) {
if (btnYes != null) return;
btnYes = dialog.findViewById(R.id.btnYes);
btnYes.setTypeface(font);
btnYes.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
dialog.dismiss();
if(button.getValue() != null)
button.getValue().onClick(view);
}
});
}
public void setTitleIconById(int iconResourceId) {
this.imgViwDialogTitle = dialog.findViewById(R.id.imgViwDialogTitle);
if (activity.getResources().getDrawable(iconResourceId) != null)
imgViwDialogTitle.setImageResource(iconResourceId);
}
public void setContentTextById(int contentResourceId) {
this.cftvContent = (CustomFontTextView)
this.dialog.findViewById(R.id.txtViwDialogContent);
if (this.activity.getString(contentResourceId) != null)
cftvContent.setText(contentResourceId);
else cftvTitle.setText(" ");
}
public void setTitleTextbyId(int titleResourceId) {
this.cftvTitle = (CustomFontTextView)
this.dialog.findViewById(R.id.txtViwDialogTitle);
if (this.activity.getString(titleResourceId) != null)
cftvTitle.setText(titleResourceId);
else cftvTitle.setText(" ");
}
public void dismiss() {
dialog.dismiss();
}
}
btnYes.setOnClickListener(button.getValue());
Change to:
btnYes.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View view) {
dialog.dismiss();
button.getValue().onClick(view);
}
});

button click not responding

This is the demo code and when I add onClickListener in fill function it is not working and if i set any other property like background color it works fine.
private void fillHolder(FriendsHolder holder, final Friend friend) {
if (friend == null)
return;
Iterator<Button> iViews = holder.interests.iterator();
Iterator<String> iInterests = friend.getInterests().iterator();
while (iViews.hasNext() && iInterests.hasNext()) {
iViews.next().setText(iInterests.next());
}
Iterator<Button> iViewss = holder.interests.iterator();
while (iViewss.hasNext()) {
iViewss.next().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), friend.getNickname(), Toast.LENGTH_SHORT).show();
}
});
}
holder.infoPage.setBackgroundColor(getResources().getColor(friend.getBackground()));
holder.nickName.setText(friend.getNickname());
}
}
You're iterating through iViewss (with double S in the end) and you're setting the listener to iViews(with single S in the end).
It's not the same object.
iViews.next().setOnClickListener() will throw a NoSuchElementException because there is not a next element.
Change your code like that:
private void fillHolder(FriendsHolder holder, final Friend friend) {
if (friend == null)
return;
Iterator<Button> iViews = holder.interests.iterator();
Iterator<String> iInterests = friend.getInterests().iterator();
while (iViews.hasNext() && iInterests.hasNext()) {
iViews.next().setText(iInterests.next());
}
while (iViews.hasNext()) {
iViews.next().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), friend.getNickname(), Toast.LENGTH_SHORT).show();
}
});
}
holder.infoPage.setBackgroundColor(getResources().getColor(friend.getBackground()));
holder.nickName.setText(friend.getNickname());
}
EDIT:
You can also combine the two while-loops (as cricket_007 suggestion):
private void fillHolder(FriendsHolder holder, final Friend friend) {
if (friend == null)
return;
Iterator<Button> iViews = holder.interests.iterator();
Iterator<String> iInterests = friend.getInterests().iterator();
while (iViews.hasNext()) {
Button button = iViews.next();
if (iInterests.hasNext()) {
button.setText(iInterests.next());
}
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), friend.getNickname(), Toast.LENGTH_SHORT).show();
}
});
}
holder.infoPage.setBackgroundColor(getResources().getColor(friend.getBackground()));
holder.nickName.setText(friend.getNickname());
}
Along with the comments from the other answer, I think this code is more appropriate - it looks like you can combine the while-loops.
private void fillHolder(FriendsHolder holder, final Friend friend) {
if (friend == null)
return;
Iterator<Button> iViews = holder.interests.iterator();
Iterator<String> iInterests = friend.getInterests().iterator();
while (iViews.hasNext()) {
Button nextButton = iViews.next();
if (iInterests.hasNext()) {
nextButton.setText(iInterests.next());
}
nextButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), friend.getNickname(), Toast.LENGTH_SHORT).show();
}
});
}
holder.infoPage.setBackgroundColor(getResources().getColor(friend.getBackground()));
holder.nickName.setText(friend.getNickname());
}

Very hard object-oriented "transplantation"in android

In my android app there are at least 4 places where the user can click a photo and then get a dialog saying "take a photo or pick from gallery to change your picture"
in each of these places its a different activity with different variables and different xml element names and I have a lot of really similar code, repeated through out my app.
I want to take it out in a separate class, but is so tangled up in the code that it feels like a heart transplantation. My object-oriented skills and Java skills arent that great (3 months experience) and maybe there is some workaround in Android that I dont know about.
I will provide two examples from different classes so you get an idea of how much repetition there is. I will be very grateful if someone could help me produce a separate object out of this
Settings class, image changer excerpt:
iUserAvatarSettings.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final Dialog dialog = new Dialog(Settings.this, R.style.no_title_dialog);
if (!userHasProvidedOwnPhoto) {
dialog.setContentView(R.layout.signup_avatar_upload_dialog);
} else {
dialog.setContentView(R.layout.signup_avatar_upload_dialog_2);
bDeleteAvatar = (Button) dialog.findViewById(R.id.bDeleteAvatar);
try {
bDeleteAvatar.setTypeface(font1);
} catch (Exception e) {
}
bDeleteAvatar.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
setAvatarPath("default_avatar");
userHasProvidedOwnPhoto = false;
if (userSex.equals("Male") || userSex.equals("")) {
iUserAvatarSettings.setImageResource(R.drawable.avatar_default_male);
} else {
iUserAvatarSettings.setImageResource(R.drawable.avatar_default_female);
}
dialog.dismiss();
}
});
}
Button bTakeAPhoto = (Button) dialog.findViewById(R.id.bTakeAPhoto);
Button bSelectPhotoFromFile = (Button) dialog.findViewById(R.id.bSelectPhotoFromFile);
Button bCancelAvatarUpload = (Button) dialog.findViewById(R.id.bCancelAvatarUpload);
try {
bTakeAPhoto.setTypeface(font1);
bSelectPhotoFromFile.setTypeface(font1);
bCancelAvatarUpload.setTypeface(font1);
} catch (Exception e) {
}
bTakeAPhoto.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (isIntentAvailable(Settings.this, MediaStore.ACTION_IMAGE_CAPTURE)) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, TAKE_IMAGE_WITH_CAMERA);
dialog.dismiss();
} else {
toastMaker.toast(net.asdqwe.activities.Settings.this, Configurationz.ErrorMessages.DEVICE_UNABLE_TO_TAKE_PHOTOS, Toast.LENGTH_LONG);
dialog.dismiss();
userHasProvidedOwnPhoto = false;
}
}
});
bSelectPhotoFromFile.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent getImageFromGallery = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
getImageFromGallery.setType("image/*");
startActivityForResult(getImageFromGallery, PICK_IMAGE);
//avatarPath = saveUserAvatar.getUserAvatar().toString();
//setAvatarPath(saveUserAvatar.getUserAvatar().toString()); // this remains under question
dialog.dismiss();
}
});
bCancelAvatarUpload.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
}); // end of image button on click handling
public static boolean isIntentAvailable(Context context, String action) {
final PackageManager packageManager = context.getPackageManager();
final Intent intent = new Intent(action);
List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
private void handleSmallCameraPhoto(Intent intent) {
try {
Bundle extras = intent.getExtras();
mImageBitmap = (Bitmap) extras.get("data");
iUserAvatarSettings.setImageBitmap(mImageBitmap);
saveUserAvatar.SaveImage(this, mImageBitmap);
userHasProvidedOwnPhoto = true;
setAvatarPath(saveUserAvatar.getUserAvatar().toString());
} catch (Exception e) {
toastMaker.toast(net.asdqwe.activities.Settings.this, Configurationz.ErrorMessages.TAKING_PHOTO_FAILED, Toast.LENGTH_LONG);
userHasProvidedOwnPhoto = false;
}
}
private void handleGalleryPhoto(Intent intent) {
try {
Uri _uri = intent.getData();
Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
cursor.moveToFirst();
final String imageFilePath = cursor.getString(0);
cursor.close();
mImageBitmap2 = BitmapFactory.decodeFile(imageFilePath);
iUserAvatarSettings.setImageBitmap(mImageBitmap2);
saveUserAvatar.SaveImage(this, mImageBitmap2);
userHasProvidedOwnPhoto = true;
setAvatarPath(saveUserAvatar.getUserAvatar().toString());
} catch (Exception e) {
toastMaker.toast(net.zxcasd.activities.Settings.this, Configurationz.ErrorMessages.PICKING_PHOTO_FROM_GALLERY_FAILED, Toast.LENGTH_LONG);
userHasProvidedOwnPhoto = false;
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TAKE_IMAGE_WITH_CAMERA && resultCode == RESULT_OK && null != data) {
handleSmallCameraPhoto(data);
} else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK && null != data) {
handleGalleryPhoto(data);
}
}
Signup class, image excerpt:
iUserAvatar.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final Dialog dialog = new Dialog(Signup.this, R.style.no_title_dialog);
if (!userHasProvidedOwnPhoto) {
dialog.setContentView(R.layout.signup_avatar_upload_dialog);
} else {
dialog.setContentView(R.layout.signup_avatar_upload_dialog_2);
bDeleteAvatar = (Button) dialog.findViewById(R.id.bDeleteAvatar);
try {
bDeleteAvatar.setTypeface(font1);
} catch (Exception e) {
}
bDeleteAvatar.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
userHasProvidedOwnPhoto = false;
if (userSex.equals("Male") || userSex.equals("")) {
iUserAvatar.setImageResource(R.drawable.avatar_default_male);
} else {
iUserAvatar.setImageResource(R.drawable.avatar_default_female);
}
dialog.dismiss();
}
});
}
Button bTakeAPhotoSignupPage = (Button) dialog.findViewById(R.id.bTakeAPhoto);
Button bSelectPhotoFromFileSignupPage = (Button) dialog.findViewById(R.id.bSelectPhotoFromFile);
Button bCancelAvatarUpload = (Button) dialog.findViewById(R.id.bCancelAvatarUpload);
try {
bTakeAPhotoSignupPage.setTypeface(font1);
bSelectPhotoFromFileSignupPage.setTypeface(font1);
bCancelAvatarUpload.setTypeface(font1);
} catch (Exception e) {
}
bTakeAPhotoSignupPage.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (isIntentAvailable(Signup.this, MediaStore.ACTION_IMAGE_CAPTURE)) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, TAKE_IMAGE_WITH_CAMERA);
dialog.dismiss();
} else {
toastMaker.toast(net.asdqwe.activities.Signup.this, Configurationz.ErrorMessages.DEVICE_UNABLE_TO_TAKE_PHOTOS, Toast.LENGTH_LONG);
dialog.dismiss();
userHasProvidedOwnPhoto = false;
}
}
});
bSelectPhotoFromFileSignupPage.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent getImageFromGallery = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
getImageFromGallery.setType("image/*");
startActivityForResult(getImageFromGallery, PICK_IMAGE);
dialog.dismiss();
}
});
bCancelAvatarUpload.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// and deal with photo
dialog.dismiss();
}
});
dialog.show();
}
});
public static boolean isIntentAvailable(Context context, String action) {
final PackageManager packageManager = context.getPackageManager();
final Intent intent = new Intent(action);
List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
private void handleSmallCameraPhoto(Intent intent) {
try {
Bundle extras = intent.getExtras();
mImageBitmap = (Bitmap) extras.get("data");
iUserAvatar.setImageBitmap(mImageBitmap);
saveUserAvatar.SaveImage(this, mImageBitmap);
userHasProvidedOwnPhoto = true;
} catch (Exception e) {
toastMaker.toast(net.asdqwe.activities.Signup.this, Configurationz.ErrorMessages.TAKING_PHOTO_FAILED, Toast.LENGTH_LONG);
userHasProvidedOwnPhoto = false;
}
}
private void handleGalleryPhoto(Intent intent) {
try {
Uri _uri = intent.getData();
Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
cursor.moveToFirst();
final String imageFilePath = cursor.getString(0);
cursor.close();
mImageBitmap2 = BitmapFactory.decodeFile(imageFilePath);
iUserAvatar.setImageBitmap(mImageBitmap2);
saveUserAvatar.SaveImage(this, mImageBitmap2);
userHasProvidedOwnPhoto = true;
} catch (Exception e) {
toastMaker.toast(net.asdqwe.activities.Signup.this, Configurationz.ErrorMessages.PICKING_PHOTO_FROM_GALLERY_FAILED, Toast.LENGTH_LONG);
userHasProvidedOwnPhoto = false;
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TAKE_IMAGE_WITH_CAMERA && resultCode == RESULT_OK && null != data) {
handleSmallCameraPhoto(data);
} else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK && null != data) {
handleGalleryPhoto(data);
}
}
Move isIntentAvailable(Context, String) into an utility class. This could be helpful in some other uses cases.
Create a abstract class (refered to as MyAbstractActivity) which contains all common activity methods: handleSmallCameraPhoto(Intent), handleGalleryPhoto(Intent) and onActivityResult(int, int, Intent)
iUserAvatarSettings / iUserAvatar must be part of MyAbstractActivity. If you need different implementations of it for different use cases, create an abstract getter, f.e. getUserAvatarButton().
setAvatarPath(String) is only used in one of the two classes. Make it an abstract method in MyAbstractActivity and implement it as needed.
Let the other classes inherit from MyAbstractActivity.
Transform the OnClickListener into a class.
As constructor arguments you pass a subclass of MyAbstractActivity and a Button (iUserAvatarSettings / iUserAvatar).
Use the constructor arguments to refer to methods and variables like userHasProvidedOwnPhoto.
The iUserAvatar.setOnClickListener(...) part will remain in MyAbstractActivity.
Now you have removed the code redundancy, but there will be more to do. Probably encapsulation isn't good enough right now.
Think about moving methods and variables to the OnClickListener class or away from it.
Think about moving methods and variables to MyAbstractActivity or away from it.
Ask some more experienced developer to make a review. (Don't ask him to refactor it.)
If you have specific refactoring question come back her and ask the community.
You can check my project Android Image Helper on Github
An activity with image selection feature would look something like this:
public class ImageUploaderActivity extends Activity implements ImageChooseCallback {
ImageUploadEngine uploadEngine;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uploadEngine = new ImageUploadEngine.Builder(this, savedInstanceState).build();
//...
someButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
uploadEngine.performImageAsk(R.string.app_name, R.string.newphoto, R.string.oldphoto, R.string.choose, R.drawable.ic_launcher);
}
});
}
#Override
public void onSaveInstanceState(Bundle outState) {
uploadEngine.onSaveInstanceState(outState);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(uploadEngine.onActivityResult(requestCode, resultCode, data)) return;
super.onActivityResult(requestCode, resultCode, data);
}
#Override
public void onCanceled(ImageUploadEngine engine) {
//notifying you that the user has canceled the request
}
#Override
public void onChosen(ImageUploadEngine engine, String path, boolean newPicture) {
//notifying you that the user has selected an image
}
#Override
public void onError(ImageUploadEngine engine, Exception ex) {
//notifying you that an error has occurred
}
}
The uploadEngine also exposes functions performImageTake and performImageChoose in case the default dialog (to choose) does not suit your needs.
FYI:
This project aims to provide an easy-to-use instrument for android developers to use when they need the user to select an image on their android phone and upload it. If the process has any errors on specific phones, these should be handled inside the library to provide the same user experience on all phones.
Here is my approach to refactor your code. I have tried to give some justification about my design as well.
1) You could abstract away the dialog into a widget.
You can create a PhotoPickerDialog that extends Dialog and provides desired functionality. Then you can re-use this widget at different places / activities.
public class PhotoPickerDialog extends Dialog {
//private fields
private Typeface buttonFont = Typeface.DEFAULT;
private Button bTakeAPhoto;
private Button bSelectPhotoFromFile;
private Button bCancelAvatarUpload;
private IMediaHelper mMediaHelper;
public PhotoPickerDialog(Context context) {
super(context);
initView();
}
public PhotoPickerDialog(Context context, int theme) {
super(context, theme);
initView();
}
protected PhotoPickerDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
initView();
}
protected void setLayoutResource() {
setContentView(R.layout.signup_avatar_upload_dialog);
}
protected void initView() {
setLayoutResource();
bTakeAPhoto = (Button) findViewById(R.id.bTakeAPhoto);
bSelectPhotoFromFile = (Button) findViewById(R.id.bSelectPhotoFromFile);
bCancelAvatarUpload = (Button) findViewById(R.id.bCancelAvatarUpload);
bCancelAvatarUpload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
}
/**
* Set the type face of the button, in case you want to have different
* font at different times.
*/
public void setButtonFont(Typeface font) {
buttonFont = font;
bTakeAPhoto.setTypeface(buttonFont);
bSelectPhotoFromFile.setTypeface(buttonFont);
bCancelAvatarUpload.setTypeface(buttonFont);
}
/**
* Set the media helper object which implements the functionality to
* take and pick photo. So the task delegated to this media helper.
*/
public void setMediaHelper(IMediaHelper mediaHelper) {
mMediaHelper = mediaHelper;
bTakeAPhoto.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mMediaHelper.takePhoto();
dismiss();
}
});
bSelectPhotoFromFile.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mMediaHelper.pickPhoto();
dismiss();
}
});
}
}
Since you need two different types of dialogs depending on whether the user has a avatar photo already or not. Let us create a PhotoPickerDialogWithDelete that extends PhotoPickerDialog. Then we just have to override a few methods and add the delete callback to make our new type of dialog without re-writing a lot of code.
public class PhotoPickerDialogWithDelete extends PhotoPickerDialog {
private Button bDeleteAvatar;
public PhotoPickerDialogWithDelete(Context context) {
super(context);
}
public PhotoPickerDialogWithDelete(Context context, int theme) {
super(context, theme);
}
protected PhotoPickerDialogWithDelete(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
#Override
protected void setLayoutResource() {
setContentView(R.layout.signup_avatar_upload_dialog_2);
}
#Override
protected void initView() {
super.initView();
bDeleteAvatar = (Button) findViewById(R.id.bDeleteAvatar);
}
#Override
public void setButtonFont(Typeface font) {
super.setButtonFont(font);
bDeleteAvatar.setTypeface(font);
}
public void setOnDeleteListener(final View.OnClickListener listener) {
bDeleteAvatar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
listener.onClick(v);
}
});
}
}
2) Move the photo intent related functionality to an abstract base class MediaSupportActivity.
All the activities that need to support taking/picking photos just need to extend from this class and implement two simple methods handleSmallCameraPhoto(Bitmap) and handleGalleryPhoto(Bitmap). They do not have to explicitly care about how to take/pick photos, but only care about how to handle them (which can be different for different activities).
public abstract class MediaSupportActivity extends Activity implements IMediaHelper {
private static final int TAKE_IMAGE_WITH_CAMERA = 1;
private static final int PICK_IMAGE = 2;
protected abstract void handleSmallCameraPhoto(Bitmap image);
protected abstract void handleGalleryPhoto(Bitmap image);
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TAKE_IMAGE_WITH_CAMERA && resultCode == RESULT_OK && null != data) {
Bundle extras = data.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");
handleSmallCameraPhoto(bitmap);
}
else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK && null != data) {
Uri _uri = data.getData();
Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
cursor.moveToFirst();
final String imageFilePath = cursor.getString(0);
cursor.close();
Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
handleGalleryPhoto(bitmap);
}
}
#Override
public void takePhoto() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, MediaSupportActivity.TAKE_IMAGE_WITH_CAMERA);
}
#Override
public void pickPhoto() {
Intent getImageFromGallery = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
getImageFromGallery.setType("image/*");
startActivityForResult(getImageFromGallery, MediaSupportActivity.PICK_IMAGE);
}
}
3) There is an interface IBBMediaHelper that defined two methods: takePhoto() and pickPhoto(). We pass a IBBMediaHelper instance to the dialog, so that that dialog does not have full access to the other properties and methods of the MediaSupportActivity
public interface IMediaHelper {
public void takePhoto();
public void pickPhoto();
}
4) Here is a sample how to use the above classes together:
public class MainActivity extends MediaSupportActivity {
Button iUserAvatarSettings;
private boolean userHasProvidedOwnPhoto = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iUserAvatarSettings = (Button) findViewById(R.id.button);
iUserAvatarSettings.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final PhotoPickerDialog dialog;
if (!userHasProvidedOwnPhoto) {
dialog = new PhotoPickerDialog(MainActivity.this);
}
else {
dialog = new PhotoPickerDialogWithDelete(MainActivity.this);
((PhotoPickerDialogWithDelete)dialog).setOnDeleteListener(onAvatarDelete);
}
dialog.setButtonFont(Typeface.MONOSPACE);
dialog.setMediaHelper(MainActivity.this);
dialog.show();
}
});
}
#Override
protected void handleSmallCameraPhoto(Bitmap image) {
// sample code
ImageView imageView = (ImageView) findViewById(R.id.imageView);
imageView.setImageBitmap(image);
}
#Override
protected void handleGalleryPhoto(Bitmap image) {
// sample code
ImageView imageView = (ImageView) findViewById(R.id.imageView);
imageView.setImageBitmap(image);
}
private OnClickListener onAvatarDelete = new OnClickListener() {
#Override
public void onClick(View v) {
// this is executed when delete is clicked
// check for male/female and update the avatar
// etc. etc.
}
};
}
Update: You can also create an avatar widget AvatarWidget class that contains an ImageView and PhotoPickerDialog. This class encapsulates the logic of displaying an avatar, clicking on it to show the menu and setting the default avatar when delete is clicked. Then in the main activity, simply declare your avatar widget via code or XML and call the setters on it when the image is received.

Getting text to speech to play on start of an activity without button

I am using text to speech in my app and I have been able to get it to work on all of my buttons fine. However, when I try to use the text to speech in my splash activity it crashes the app. It is a nullpointer exception so I know I am just coding it incorrectly. To clarify what I want it to do. I want it to talk during the splash activity. When the splash activity sleeps I want it to talk again to tell the user it is done loading.I have included the java for my splash activity.
public class mainj extends Activity implements OnInitListener {
private TextToSpeech myTTS;
// status check code
private int MY_DATA_CHECK_CODE = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.loadscreen);
Thread logoTimer = new Thread() {
public void run() {
try {
try {
sleep(5000);
speakWords("loading");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Intent menuIntent = new Intent("android.intent.action.MENU");
startActivity(menuIntent);
Intent checkTTSIntent = new Intent();
checkTTSIntent
.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
}
finally {
finish();
}
}
};
logoTimer.start();
}
// speak the user text
private void speakWords(String speech) {
// speak straight away
myTTS.speak(speech, TextToSpeech.QUEUE_FLUSH, null);
}
// act on result of TTS data check
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
// the user has the necessary data - create the TTS
myTTS = new TextToSpeech(this, this);
} else {
// no data - install it now
Intent installTTSIntent = new Intent();
installTTSIntent
.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installTTSIntent);
}
}
}
// setup TTS
public void onInit(int initStatus) {
// check for successful instantiation
if (initStatus == TextToSpeech.SUCCESS) {
if (myTTS.isLanguageAvailable(Locale.US) == TextToSpeech.LANG_AVAILABLE)
myTTS.setLanguage(Locale.US);
} else if (initStatus == TextToSpeech.ERROR) {
Toast.makeText(this, "Sorry! Text To Speech failed...",
Toast.LENGTH_LONG).show();
}
}
}
Right at the beginning of your thread after you sleep you are calling speakWords. that calls myTTS.speak. At that point looking at your code, the myTTS does not seem to be initialized and is null so will crash with an NPE.
This code should prevent the NPE, but if the initialization of the TTS engine takes too long, then you won't get it to say Loading. Also, I am guessing the 5 second (which is a really long time btw) sleep is to allow for it to get initialized?
public class mainj extends Activity implements OnInitListener {
private TextToSpeech myTTS;
// status check code
private int MY_DATA_CHECK_CODE = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.loadscreen);
Intent checkTTSIntent = new Intent();
checkTTSIntent
.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
Thread logoTimer = new Thread() {
public void run() {
try {
try {
sleep(5000);
speakWords("loading");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Intent menuIntent = new Intent("android.intent.action.MENU");
startActivity(menuIntent);
}
finally {
finish();
}
}
};
logoTimer.start();
}
// speak the user text
private void speakWords(String speech) {
// speak straight away
if(myTTS != null)
{
myTTS.speak(speech, TextToSpeech.QUEUE_FLUSH, null);
}
}
// act on result of TTS data check
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
// the user has the necessary data - create the TTS
myTTS = new TextToSpeech(this, this);
} else {
// no data - install it now
Intent installTTSIntent = new Intent();
installTTSIntent
.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installTTSIntent);
}
}
}
// setup TTS
public void onInit(int initStatus) {
// check for successful instantiation
if (initStatus == TextToSpeech.SUCCESS) {
if (myTTS.isLanguageAvailable(Locale.US) == TextToSpeech.LANG_AVAILABLE)
myTTS.setLanguage(Locale.US);
} else if (initStatus == TextToSpeech.ERROR) {
Toast.makeText(this, "Sorry! Text To Speech failed...",
Toast.LENGTH_LONG).show();
}
}
}
You would be better calling you're little loading snippet after the TTS engine has been loaded, so you could put the speak in OnActivityResult().
In your code you can't actually tell whether or not myTTS has been initialised when you call speak. Try it this way:
Intent menuIntent = new Intent("android.intent.action.MENU");
startActivity(menuIntent);
Intent checkTTSIntent = new Intent();
checkTTSIntent
.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
try {
sleep(5000);
speakWords("loading");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
There are two things wrong with your approach.
First, your app needs to wait for init() before it tries to speak.
Second, your app needs to handle when the speech libraries are not available.
Use this class or this one to help with the TextToSpeech initialization. It's actually kind of complex and using TextToSpeech.Engine.ACTION_CHECK_TTS_DATA is actually NOT the best way to do it.

Categories