Very hard object-oriented "transplantation"in android - java

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.

Related

Open another activity when dialog's button is pressed?

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'

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.

Voice Recognition without Google Dialog

I am trying to use the Speech recognition without dialog with RecognitionListener, but it doesn't work. The following codes are the way now I use, and the bottom codes (test part) are for checking that could phone receive my commanding. Additionally, I have added permissions Audio. Finally, I don't want there have any button.
This is my first time asking question, so hope I dont violate any unspoken rules.
Thank you very much,
public class Step extends AppCompatActivity {
private final int SPEECH_RECOGNITION_CODE = 1;
private TextView txtOutput;
private Button btnMicrophone;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_step);
txtOutput = (TextView) findViewById(R.id.txt_output);
btnMicrophone = (Button) findViewById(R.id.btn_mic);
startSpeechToText();
btnMicrophone.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startSpeechToText();
}
});
}
private void startSpeechToText() {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "SPEAK");
try {
startActivityForResult(intent, SPEECH_RECOGNITION_CODE);
} catch (ActivityNotFoundException a) {
Toast.makeText(getApplicationContext(), "Sorry! Speech recognition is not supported in this device.", Toast.LENGTH_SHORT).show();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case SPEECH_RECOGNITION_CODE: {
if (resultCode == RESULT_OK && null != data) {
ArrayList<String> result = data
.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
String text = result.get(0);
txtOutput.setText(text);
test(txtOutput.getText().toString());
startSpeechToText();
}
break;
}
}
}
public void test (String com) {
for(int i=0;i<com.length();i++) {
if (com.startsWith("good",i)) {
com = com + "123";
Toast.makeText(CookStep.this, com, Toast.LENGTH_SHORT).show();
}
}
if(com.compareToIgnoreCase("OK")==0){
com=com+"456";
Toast.makeText(CookStep.this, com, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(CookStep.this, com, Toast.LENGTH_SHORT).show();
}
}
}

cannot run android text to speech

My code is as follows
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runBtn = (Button)findViewById(R.id.button_run_demo);
ttobj = new TextToSpeech(getApplicationContext(),
new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if (status != TextToSpeech.ERROR) {
ttobj.setLanguage(Locale.UK);
}
}
});
runBtn.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST);
}
});
}
public void onPause() {
if (ttobj != null) {
ttobj.stop();
ttobj.shutdown();
}
super.onPause();
}
public void speakText(String text) {
String toSpeak = text;
Toast.makeText(getApplicationContext(), toSpeak,
Toast.LENGTH_SHORT).show();
ttobj.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_PIC_REQUEST) {
//do my stuff
speakText("Hello");
}
}
Although I am able to do my stuff I am not able to run speech.
Am I missing something ??
Thanks All
When onActivityResult() is called the TextToSpeech is already shutdown since you shut it down in onPause(). You have to either set a flag to speak "Hello" when TextToSpeech is reinitialize or put your code in onPause() in onDestroy().

In App Purchases Not Saving Shared Preferences

I have an android application similar to wheel of fortune where users have the option to purchase one consumable, $1000, and two entitlements, where they unlock two images as wheel styles. I am using the Amazon In-App Purchasing API. The user should be able to purchase as many consumables as they want but once they purchase the entitlements the unlocked image should be the only image that they see and they should no longer see the locked image. These in-app purchases work fine the first instance I initiate these purchases.
However, the consumable field will only update once and even though I can still go through the process of completing purchases for the consumable, the text view containing the score, or money, does not update other then that first initial purchase. Also the wheels return to the locked image rather then remaining as the unlocked image despite the fact that when I initiate the purchase for these entitlements I am told that I already own these items. Therefore I believe it may be something to do with my SharedPreferences. In short my purchases update my views once and then never again, however the backend code i.e the responses I receive from the Amazon client when completing purchases are correct. Can anyone see where I have made a mistake? Why does the textView containing the score update on the 1st purchase and never again from then on? Also how do I save the changes toe the wheel style so that when it reopens they no longer have the option to purchase the wheel? I have three classes and have included the code below. All and any help is greatly appreciated.
Game Class
public class Game extends Activity {
private ImageView wheel;
private int rand;
private int[] amounts = {100,650,-1,650,300,-1,800,250,-1,500};
private int score = 0;
private TextView scoreText;
private AnimatorSet set;
protected boolean animationDone = true;
private SharedPreferences prefs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
prefs.registerOnSharedPreferenceChangeListener(prefsChanged);
wheel = (ImageView) findViewById(R.id.imageView1);
scoreText = (TextView) findViewById(R.id.score);
score = prefs.getInt("score", 0);
scoreText.setText("$" + String.valueOf(score));
}
private OnSharedPreferenceChangeListener prefsChanged = new OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences prefs,
String key) {
if(key.equals("money") && prefs.getBoolean(key, false)) {
score += 1000;
scoreText.setText("$" + String.valueOf(score));
prefs.edit().putBoolean("money", false);
}
}
};
#Override
protected void onStart() {
super.onStart();
InAppObserver obs = new InAppObserver(this);
PurchasingManager.registerObserver(obs);
}
#Override
protected void onPause() {
if(this.isFinishing())
{
prefs.edit().putInt("score", score).commit();
}
super.onPause();
}
#Override
protected void onStop() {
prefs.edit().putInt("score", score).commit();
super.onStop();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode != RESULT_CANCELED) {
String style = data.getStringExtra("wheel");
if(style.equals("camo"))
wheel.setImageResource(R.drawable.camowheel);
if(style.equals("gold"))
wheel.setImageResource(R.drawable.goldwheel);
if(style.equals("normal"))
wheel.setImageResource(R.drawable.wheel);
}
}
public void spinTheWheel(View v) {
if(animationDone) {
wheel.setRotation(0);
rand = (int) Math.round(2000 + Math.random()*360);
set = new AnimatorSet();
set.play(ObjectAnimator.ofFloat(wheel, View.ROTATION, rand));
set.setDuration(2000);
set.setInterpolator(new DecelerateInterpolator());
set.start();
animationDone = false;
set.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
calculateResult();
animationDone = true;
}
});
}
}
private void calculateResult() {
int angle = (int) wheel.getRotation();
angle %= 360;
angle = (int) Math.floor(angle/36);
if(amounts[angle] == -1) {
Intent intent = new Intent(this, GameOver.class);
intent.putExtra("score", score);
prefs.edit().putInt("score", 0).commit();
score = 0;
startActivity(intent);
}
else {
score += amounts[angle];
scoreText.setText("$"+String.valueOf(score));
prefs.edit().putInt("score", 0).commit();
}
}
public void upgradeWheel(View v) {
Intent intent = new Intent(getApplicationContext(), ChangeWheel.class);
startActivityForResult(intent, 1);
}
public void endGame(View v) {
Intent intent = new Intent(getApplicationContext(), GameOver.class);
intent.putExtra("score", score);
prefs.edit().putInt("score", 0).commit();
score = 0;
startActivity(intent);
}
public void addMoney(View v) {
PurchasingManager.initiatePurchaseRequest("money");
}
}
ChangeWheel Class
public class ChangeWheel extends Activity {
private Button buyCamoButton;
private Button buyGoldButton;
private ImageButton goldButton;
private ImageButton camoButton;
private SharedPreferences prefs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_change_wheel);
prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
prefs.registerOnSharedPreferenceChangeListener(prefsChanged);
buyCamoButton = (Button) findViewById(R.id.buyCamo);
buyGoldButton = (Button) findViewById(R.id.buyGold);
goldButton = (ImageButton) findViewById(R.id.goldButton);
camoButton = (ImageButton) findViewById(R.id.camoButton);
goldButton.setEnabled(false);
camoButton.setEnabled(false);
}
private OnSharedPreferenceChangeListener prefsChanged = new OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences prefs,
String key) {
if(key.equals("camo") && prefs.getBoolean(key, false)) {
camoButton.setImageResource(R.drawable.camowheel);
camoButton.setEnabled(true);
buyCamoButton.setVisibility(View.INVISIBLE);
}
else if(key.equals("gold") && prefs.getBoolean(key, false)) {
goldButton.setImageResource(R.drawable.goldwheel);
goldButton.setEnabled(true);
buyGoldButton.setVisibility(View.INVISIBLE);
}
}
};
#Override
protected void onStart() {
super.onStart();
InAppObserver obs = new InAppObserver(this);
PurchasingManager.registerObserver(obs);
}
public void camoClick(View v) {
Intent intent = new Intent(getApplicationContext(), Game.class);
intent.putExtra("wheel", "camo");
setResult(RESULT_OK, intent);
finish();
}
public void goldClick(View v) {
Intent intent = new Intent(getApplicationContext(), Game.class);
intent.putExtra("wheel", "gold");
setResult(RESULT_OK, intent);
finish();
}
public void normalClick(View v) {
Intent intent = new Intent(getApplicationContext(), Game.class);
intent.putExtra("wheel", "normal");
setResult(RESULT_OK, intent);
finish();
}
public void buyCamo(View v) {
String req = PurchasingManager.initiatePurchaseRequest("camo");
prefs.edit().putString(req, "camo").commit();
}
public void buyGold(View v) {
String req = PurchasingManager.initiatePurchaseRequest("gold");
prefs.edit().putString(req, "gold").commit();
}
}
InAppObserver Class
public class InAppObserver extends BasePurchasingObserver {
private SharedPreferences prefs;
public InAppObserver(Activity caller) {
super(caller);
prefs = PreferenceManager.getDefaultSharedPreferences(caller.getApplicationContext());
}
#Override
public void onSdkAvailable(boolean isSandboxMode) {
PurchasingManager.initiatePurchaseUpdatesRequest(Offset.BEGINNING);
}
#Override
public void onPurchaseUpdatesResponse(PurchaseUpdatesResponse res) {
for(String sku : res.getRevokedSkus()) {
prefs.edit().putBoolean(sku, false).commit();
}
switch (res.getPurchaseUpdatesRequestStatus()) {
case SUCCESSFUL:
for(Receipt rec : res.getReceipts()) {
prefs.edit().putBoolean(rec.getSku(), true).commit();
}
break;
case FAILED:
// do something
break;
}
}
#Override
public void onPurchaseResponse(PurchaseResponse res) {
switch(res.getPurchaseRequestStatus()) {
case SUCCESSFUL:
String sku = res.getReceipt().getSku();
prefs.edit().putBoolean(sku, true).commit();
break;
case ALREADY_ENTITLED:
String req = res.getRequestId();
prefs.edit().putBoolean(prefs.getString(req, null), true).commit();
break;
case FAILED:
// do something
break;
case INVALID_SKU:
// do something
break;
}
}
}
It could be that you are not using the same editor.
preferences.edit().putString(PreferenceKey.DISTANCE, distance);
preferences.edit().commit();
two different SharedPreferences.Editors are being returned. Hence the
value is not being committed. Instead, you have to use:
SharedPreferences.Editor spe = preferences.edit();
spe.putString(PreferenceKey.DISTANCE, distance);
spe.commit();
From... SharedPreferences not working across Activities

Categories