How to limit a "Rate This Application" fragment from constantly displaying - android-fragments

I want to have a fragment that asks a user to rate my application when they close it. The fragment works fine but can be annoying especially if it comes up the first few times the user closes the application. I want them to use the application, say 5 times, before it appears. How can this be done using a global holder?

This is the solution I came up with. I'm sure everyone will tell me using globals is not the best way to do this. But this worked for me. In my Main Activity I put the following code:
#Override
public void onBackPressed() {
int theCount = globalHolder.getCounter();
globalHolder.setCounter(theCount++);
if (globalHolder.getStop() == 1) {
finish();
} else if (theCount == 5 || theCount == 10 || theCount == 25) {
globalHolder.setCounter(theCount++);
final DialogFragment rate = new RateMe();
rate.show(getSupportFragmentManager(), "Rate");
} else {
globalHolder.setCounter(theCount++);
finish();
}
}
Now, let me explain this. The first global is a counter. I added this because I want to keep track of the number of times the user uses the app. You will see why in a minute. The counter initializes at 0. So since the user is accessing the program for the first time I increment the counter.
The next line of code is put in so that I won't annoy users who don't want to rate the app or who have already agreed to rate it. This is all you can do. You cannot tell if they actually rated the app but you can determine if they push the button to do so. So I have to assume that since they pressed the "Yes" button that they actually rated the app. And now I want to stop displaying the fragment. So I set up another global called....stop. I set this global to "0" (for "no"). Yes, I used an int. That was my choice. You can use what you want. So when the global is changed to "1" it increments the counter and then closes the program.
The 'else if' is where i set my limits of when I want the fragment to display. It is set to display the fragment on the users 5th, 10th, and 25th use of the application only. If they have not rated the app by the 25th use they are most likely not going to. From this point the counter, again, is incremented (you will see why later) and the fragment is displayed on the counts above.
The next 'else' increments the counter and closes the program.
The fragment is:
public class RateMe extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setCancelable(false);
builder.setTitle("Rate Me");
builder.setMessage(R.string.Rate);
builder.setPositiveButton("Yes, I Will", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((MainActivity)getActivity()).globalHolder.setStop(1);
Uri uri = Uri.parse("market://details?id=wolfEnterprisesLLC.motg&hl=en");
Intent myAppLinkToMarket = new Intent(Intent.ACTION_VIEW, uri);
myAppLinkToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
try {
startActivity(myAppLinkToMarket);
} catch (ActivityNotFoundException e) {
Toast.makeText(getContext(), " unable to find market app", Toast.LENGTH_LONG).show();
}
}
});
builder.setNeutralButton("Please Do Not Ask Again", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((MainActivity)getActivity()).globalHolder.setStop(1);
getActivity().finish();
}
});
builder.setNegativeButton("Not Now", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
});
return builder.create();
}
}
On positive click the global named "stop" is set to "1". From this point the user is sent to Play Store to rate the app.
The "neutral" button which the user can push if they don't want to be asked again is also sets the global "stop" to "1". From here the program closes. Setting the global to "1", again, stops the fragment from appearing in the future.
The Negative button, which is "Not Now", closes the program. Looking back at the Main Activity remember the counter? This is why I want the counter to continue. So if the press the negative button it will still keep count and ask them again on the 10th and 25th use to rate the application if necessary.
And finally in my application class I added:
int stop;
int counter;
public int getStop() {
return stop;
}
public void setStop(int stop) {
this.stop = stop;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
So I help this helps someone.
:)

Related

Navigation Drawer: how make fragments persistent (keep alive) while switching (not rotating)

With Fragment:setRetainInstance(true); the fragment is not re-instantiated on a phones orientation change.
And of course i want my fragments to be kept alive while switching from one fragment to another.
But the Android Studio 4 provides a wizard-template with only
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
From hours of debugging and searching the net if think it would need to inherent from the class FragmentNavigator so i can overwrite FragmentNavigator:naviagte where a new fragment gets created via final Fragment frag = instantiateFragment(.. and then is added with ft.replace(mContainerId, frag);
So i could find my old fragment and use ftNew.show and ftOld.hide instead.
Of course this is a stupid idea, because this navigate method is full of other internal stuff.
And i have no idea where that FrameNavigator is created.
I can retrieve it in the MainActivity:OnCreate with
NavigatorProvider navProvider = navController.getNavigatorProvider ();
Navigator<NavDestination> navigator = navProvider.getNavigator("fragment");
But at that time i could only replace it with my derived version. And there is no replaceNavigtor method but only a addNavigator method, which is called where ?
And anyways this all will be far to complicated and therefore error prone.
Why is there no simple option to keep my fragments alive :-(
In older Wizard-Templates there was the possibility of
#Override
public void onNavigationDrawerItemSelected(int position) {
Fragment fragment;
switch (position) {
case 1:
fragment = fragment1;
break;
case 2:
fragment = fragment2;
break;
case 3:
fragment = fragment3;
break;
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(mCurrentFragment == null) {
ft.add(R.id.container, fragment).commit();
mCurrentFragment = fragment;
} else if(fragment.isAdded()) {
ft.hide(mCurrentFragment).show(fragment).commit();
} else {
ft.hide(mCurrentFragment).add(R.id.container, fragment).commit();
}
mCurrentFragment = fragment;
}
but i have no idea how to do this with the Android 4.0 template where my MainActivity is only derived as:
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
Ideas welcome :'(
Hi there & sorry for my late answer! I had a similar problem with navigation drawers and navigation component. I tried around a little and found a working solution, which might be helpful for others too.
The key is the usage of a custom FragmentFactory in the FragmentManager of the MainActivity. See the code for this below:
public class StaticFragmentFactory extends FragmentFactory {
private myNavHostFragment1 tripNavHostFragment;
private myNavHostFragment2 settingsNavHostFragment;
#NonNull
#Override
public Fragment instantiate(#NonNull ClassLoader classLoader, #NonNull String className) {
if (MyNavHostFragment1.class.getName().equals(className)) {
if (this.myNavHostFragment1 == null) {
this.myNavHostFragment1 = new MyNavHostFragment1();
}
return this.myNavHostFragment1 ;
} else if (MyNavHostFragment2.class.getName().equals(className)) {
if (this.myNavHostFragment2 == null) {
this.myNavHostFragment2 = new MyNavHostFragment2();
}
return this.myNavHostFragment2;
}
return super.instantiate(classLoader, className);
}
}
The FragmentFactory survives the navigation between different fragments using the NavigationComponent of AndroidX. To keep the fragments alive, the FragmentFactory stores an instance of the fragments which should survive and returns this instance if this is not null. You can find a similar pattern when using a singleton pattern in classes.
You have to register the FragmentFactory in the corresponding activity by calling
this.getSupportFragmentManager().setFragmentFactory(new StaticFragmentFactory())
Please note also that I'm using nesten fragments here, so one toplevel fragment (called NavHostFragmen here) contains multiple child fragments. All fragments are using the same FragmentFactory of their parent fragments. The custom FragmentFactory above returns the result of the super class method, when the fragment to be instantiated is not known to keep alive.

Why every KeyEvent is successfully captured except KEYCODE.ENTER in my JavaFX Calculator app?

I'm writing a simple calculator app with JavaFX. Every keyboard event is successfully captured except the ENTER key. The EQUALS ("=") is the only hardware key that will trigger the calculation function. I have scoured every question on this site that even remotely deals with with keyboard event handling, I have pored over the documentation on Oracle's site to no avail. below is are some excerpts from my code (I left out most of the lines that check every possible KeyCode for brevity):
#FXML
private void handleKeyboardEvent(KeyEvent event) {
KeyCode keyCode = event.getCode();
if (event.getCharacter().equals("\r") || event.getCharacter().equals("\n")) {
onEqualsButtonPressed();
}
switch (keyCode) {
case ESCAPE:
textFieldDisplayEquation.clear();
textFieldDisplayResult.clear();
break;
case EQUALS:
if (event.isShiftDown()) {
if (textFieldDisplayEquation.getText().length() == 0) {
textFieldDisplayEquation.clear();
break;
} else {
textFieldDisplayEquation.appendText(" + ");
break;
}
} else {
onEqualsButtonPressed();
break;
}
case ENTER:
onEqualsButtonPressed();
break;
}
}
when the ENTER key is pressed, the text previously entered into the TextField simply vanishes.
As I said, every other keyboard onKeyPress event is successfully captured and the calculator works perfectly (including clicking GUI buttons)! This is driving me crazy. Any advice would be appreciated.
Ok. It seems that whenever the two textfields are cleared, you have to tell the GridPane to request focus. Having done that, the ENTER key works every time
Below are the relevant code snippets:
#FXML
private GridPane root;
#FXML
private void onButtonClearClicked() {
labelMessage.setText("");
textFieldDisplayEquation.clear();
textFieldDisplayResult.clear();
root.requestFocus();
}
Thank you to everyone who took the time to answer or comment!

JavaFX : ChangeListener to get the resolution of screen on mouse released

How do I get the resolution of the screen application (during resize) but only once the mouse has been released ?
I've looked around, but I found nothing. I've done this :
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
ConfigBusiness.getInstance().setScreenWidth(newValue.intValue());
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
ConfigBusiness.getInstance().setScreenHeight(newValue.intValue());
}
});
But as you expect, everytime the width / height changes, it calls the function to save the value (in my case, in the registry, which results in many calls).
Is there a way to get the value only when the mouse button has been released ? Or maybe use another kind of listener (setOnMouseDragRelease or something like that) ?
EDIT : I want the user to be able to resize the application window, and once he releases the mouse button after resizing I would like to trigger an event (and not during the whole resizing process).
Thanks
EDIT 2 : Following a bit the idea of #Tomas Bisciak, I came up with this idea.
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(!widthHasChanged()){
setWidthChanged(true);
System.out.println("Width changed");
}
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(!heightHasChanged()){
setHeightChanged(true);
System.out.println("Height changed");
}
}
});
scene.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(widthHasChanged()){
ConfigBusiness.getInstance().setScreenWidth(scene.getWidth());
System.out.println("Width changed > release");
setWidthChanged(false);
}
if(heightHasChanged()){
ConfigBusiness.getInstance().setScreenHeight(scene.getHeight());
System.out.println("Height changed > release");
setHeightChanged(false);
}
}
});
I flagged the changes done during the resizing process (with the help of widthProperty and heightProperty) and set them to true if changed, and then when releasing the mouse, I set the values if they have changed.
The problem is that the last event MOUSE_RELEASED is not triggered. I see the output from the changeListeners but not the eventFilter. Any ideas ?
Hook listener onto scene Handle mouse event anywhere with JavaFX (use MouseEvent.MOUSE_RELEASED) and on mouse release write values of the setScreenHeight/width(on every mouse release ),
if you want to only write values when drag happened with intention of resize , use boolean flag in change() "flag=true on change" methods to indicate that change has started, aferwards on mouse release just write values wherever you want and set flag to (flag=false).
If you want every time the mouse is released to get the resolution of the Monitor Screen here is the code:
Assuming you have the code for the Stage and Scene you can add a Listener for MouseReleased Event like this:
public double screenWidth ;
public double screenHeight;
.......
scene.setOnMouseReleased(m->{
screenWidth = Screen.getPrimary().getBounds().getWidth();
screenHeight = Screen.getPrimary().getBounds().getHeight();
});
......
In case you have an undecorated window that you resize it manually
when the mouse is dragged i recommend you using setOnMouseDragged();
Assuming that you are doing the above this code for moving the window
manually can be useful:
public int initialX;
public int initialY;
public double screenWidth = Screen.getPrimary().getBounds().getWidth() ;
public double screenHeight = Screen.getPrimary().getBounds().getHeight();
setOnMousePressed(m -> {
if (m.getButton() == MouseButton.PRIMARY) {
if (m.getClickCount() == 1) { // one Click
setCursor(Cursor.MOVE);
if (window.getWidth() < screenWidth ) {
initialX = (int) (window.getX() - m.getScreenX());
initialY = (int) (window.getY() - m.getScreenY());
} else
setFullScreen(false);
} else if (m.getClickCount() == 2) // two clicks
setFullScreen(true);
}
});
setOnMouseDragged(m -> {
if (window.getWidth() < screenWidth && m.getButton() == MouseButton.PRIMARY) {
window.setX(m.getScreenX() + initialX);
window.setY(m.getScreenY() + initialY);
}
});
setOnMouseReleased(m -> setCursor(Cursor.DEFAULT));
I finally found a way to accomplish "more or less" what I wanted. Here is the code for those who are looking for how to do it.
ChangeListener<Number> resizeListener = new ChangeListener<Number>() {
Timer timer = null; // used to schedule the saving task
final long delay = 200; // the delay between 2 changes
TimerTask task = null; // the task : saves resolution values
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
// cancels the old task as a new one has been queried
// at every change, we cancel the old task and start a new one
if(task != null) task.cancel();
// reset of the timer
if(timer == null) timer = new Timer();
task = new TimerTask() {
#Override
public void run() {
ConfigBusiness.getInstance().setScreenWidth(scene.getWidth());
ConfigBusiness.getInstance().setScreenHeight(scene.getHeight());
// stopping the timer once values are set
timer.cancel();
timer = null;
// stopping the task once values are set
task.cancel();
task = null;
}
};
timer.schedule(task, delay);
}
};
scene.widthProperty().addListener(resizeListener);
scene.heightProperty().addListener(resizeListener);
I added some comments just to help understanding the way it works. During resizing, a timer schedules a task to retrieve the width / height of the window and is started.
If another changes incomes, then the task is "reset" in order to get only the last values set.
Once the values are retrieved, it is important to clear the timer and the task, else threads will continue running.
Thanks to all who gave me hints on how to do it, hope this helps !

How to wait for user's action in JavaFX (in the same stage)

Do you know how to wait for the user's input in a for loop? I don't mean the showAndWait() method, because I am not opening a new dialogue stage for the user. So for example, each round of the for loop should be waiting for the user to push a button before going ahead with the next round.
How is it possible? Many thanks!
UPDATE:
Now it came to my mind, that it would work with a while(buttonNotPressed){} but is it a good solution? I mean the while loop is running in this case as crazy until the user won't push the button. Or doest it work somehow similarly with wait methods?
Imagine it as a session:
User starts session with handleStart() You give the user 5 questions, one after one. In every iteration, the user can answer the upcoming question and he can save or submit the answer by handleSaveButton() You process the answer as you want, and go ahead with the next iteration. The point is, that the iteration must stop, until the save button hasn't been pressed.
Don't do it like that. The FX toolkit, like any event-driven GUI toolkit, already implements a loop for the purposes of rendering the scene graph and processing user input each iteration.
Just register a listener with the button, and do whatever you need to do when the button is pressed:
button.setOnAction(event -> {
// your code here...
});
If you want the action to change, just change the state of some variable each time the action is performed:
private int round = 0 ;
// ...
button.setOnAction(event -> {
if (round < 5) {
System.out.println("Round "+round);
System.out.println("User's input: "+textArea.getText());
round++ ;
}
});
I recently ran into a similar problem where I wanted something to be executed with an interval (if that's what you mean), until the user fired an event. I found 3 ways to do this:
UPDATE
You should use the stop/cancel method for the custom runnable and timer or else the thread will still be running when you exit the application. Timeline seems do it by itself.
Using a Timer:
Timer timer = new Timer();
TimerTask task = new TimerTask() {
#Override
public void run() {
System.out.println("Printed every second.");
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
//timer.cancel();
With a TimeLine:
Timeline tl = new Timeline(new KeyFrame(Duration.millis(1000), e -> {
System.out.println("Timeline");
}));
tl.setCycleCount(Timeline.INDEFINITE);
tl.play();
//tl.stop();
Or making your own runnable class:
public class Runner implements Runnable {
private final Thread thread = new Thread(this);
private boolean run;
#Override
public void run() {
while(run) {
try {
System.out.println("Printed from loop");
Thread.sleep(1000);
} catch (InterruptedException e) {
run = false;
}
}
}
public void start() {
run = true;
thread.start();
}
public void stop() {
if(run) {
thread.interrupt();
System.out.print("Thread has stopped.");
}
}
}
And then when a person clicks fx. a button the event would stop using the example James_D posted:
Button btn = new Button("Button");
btn.setOnAction(e -> {
timer.cancel();
tl.stop();
runner.stop();
});
In my case, for inside for, had to create 2 index in class, use:
//start method
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
public void run()
{
Platform.runLater(()->{
//... code to run after time, calling the same mehtod, with condition to stop
});
}
};
timer.schedule(task, time);
//end method
Had to use recursive method, incrementing the index with conditions, cause the tasks were been schedule all at the same time, without wait time.
I do not know if it is rigth, but was the solution that i found.
Hope it helps.
ALTERNATIVE SOLUTION W/O PAUSING:
I'm creating a game where I want the user to pick the game difficulty before the game starts. Instead of trying to pause the program midway through, I just put the next step of the code in a separate method which you call once a button is clicked:
private static difficulty;
public static void main(String[] args) {
try {
Application.launch(args);
} catch (UnsupportedOperationException e) {
}
}
public void start(Stage startStage) {
HBox buttons = new HBox();
Button easyButton = new Button("Easy");
Button mediumButton = new Button("Medium");
Button hardButton = new Button("Hard");
buttons.getChildren().addAll(easyButton, mediumButton, hardButton);
buttons.setAlignment(Pos.CENTER);
hbox.getChildren().addAll(buttons);
Scene startScene = new Scene(buttons, 200, 200);
startStage.setScene(startScene);
startStage.show(); // MENU
EventHandler<ActionEvent> playEasy = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 1; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON EASY
}
};
EventHandler<ActionEvent> playMedium = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 2; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON MEDIUM
}
};
EventHandler<ActionEvent> playHard = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 3; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON HARD
}
};
easyButton.setOnAction(playEasy);
mediumButton.setOnAction(playMedium);
hardButton.setOnAction(playHard);
}
public void play() {
// WRITE GAME CODE HERE
}
To solve your specific problem, you could probably pass the startStage into the play method and then just update the scene there...but regardless I do hope this helps someone whos having trouble on how to use buttons! :)

Buttons with state android

Good evening,
I ask myself the following head breaks:
I would like to create a Favorites button that has the two following states:
1st state: "Add to Favorites"
2nd state (on OnClick event): "Remove from favorites"
But I'd also be able to return to the 2nd state: "Add to Favorites" by a 2nd OnClick event ect..
Does anyone have a solution for it with a simple OnClickListener it seems impossible.
I finally solved my problem using a custom checkbox !!Is the best way to use favorite things because you can get the state of your drawable !!
like that :
favoris_button = (CheckBox) findViewById(R.id.star);
favoris_button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (((CheckBox) v).isChecked()) {
favoris_button.setText("Supprimer des categories");
}
else
favoris_button.setText("Ajouter aux favoris");
}
});
Run this code inside your listener or wrap a function around it and pass it the boolean and call it when you need to. Startup, reset or whenever you want to change the state. If the button is clicked you need to flip the state.
private Boolean check;
favoris_button = (Button) findViewById(R.id.promotion_favoris);
favoris_button.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if (check == true)
{
favoris_button.setBackgroundColor(R.drawable.btn_orange9);
favoris_button.setText("Supprimer des favoris");
}
else
{
favoris_button.setBackgroundColor(R.drawable.btn_red9);
favoris_button.setText("Ajouter aux favoris");
}
check ^= true;
}
});

Resources