Android Review Manager - android

apologies in advance, I'm more of a iOS developer than Android and despite reading various tutorials I'm really struggling to create a simple Google review prompt in my app.
I'm using ReviewManagerFactory. I've adde the build.gradle dependency
implementation 'com.google.android.play:core:1.8.0'
Within my code, on one activity I have a simple function called 'reviewPrompt'. Creating an instance of ReviewManager and requesting start review flow.
private void reviewPrompt() {
ReviewManager manager = ReviewManagerFactory.create(this);
manager.requestReviewFlow();
}
Is this all I need to do to achieve a review prompt? A lot of other tutorials (including Google ) keep talking about creating an instance of ReviewManagerFactory first
ReviewManager manager = ReviewManagerFactory.create(context)
And then adding this:
ReviewManager manager = ReviewManagerFactory.create(this);
Task<ReviewInfo> request = manager.requestReviewFlow();
request.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
// We can get the ReviewInfo object
ReviewInfo reviewInfo = task.getResult();
} else {
// There was some problem, log or handle the error code.
#ReviewErrorCode int reviewErrorCode = ((TaskException) task.getException()).getErrorCode();
}
});
Why are they advising creating 2 instances of ReviewManger? Firstly with (context) and then with (this)? If both are needed do they both go in the same function or does
ReviewManager manager = ReviewManagerFactory.create(context)
Go somewhere else??
They also seem to attach listeners etc (in various ways), but as far as I can see that isn't necessarily needed unless I wish to track whether the review flow completed or failed etc.
Please correct me if I'm missing anything here.
Finally, I wish to call this function only when a user has opened a particular activity 'x' number of times in the app's lifetime. I my iOS I used User Defaults and reviewWorthyActionCount. Is there anything similar in Android?
Thanks in advance

according to ReviewManagerFactory , you should pass context , by the way android activity is context too . when you mention "this" it means you pass current object(the activity ) . you don't need to create 2 diffrenet objects using this and context ( both are same)
for second case , first save a value called fooActivityVisited in android SharedPereference ( its a local storage to store values) , then in your activity you increate it and check when reached to "Count" you will show the review

The example on the documentation is separated into 3 sections, it can be a bit hard to understand if you are new to android. Here you have a fully working example integrating the 3 sections from the documentation.
ReviewManager manager = ReviewManagerFactory.create(this);
Task<ReviewInfo> request = manager.requestReviewFlow();
request.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
ReviewInfo reviewInfo = task.getResult();
val flow = manager.launchReviewFlow(this#/*Your activity class name here(ie:MainActivity)*/, reviewInfo)
flow.addOnCompleteListener { _ ->
//do something after the review flow finishes, like logging that the review flow was completed successfully
}
} else {
// There was some problem, log or handle the error code.
#ReviewErrorCode int reviewErrorCode = ((TaskException) task.getException()).getErrorCode();
}
});
Explanation:
Both
ReviewManager manager = ReviewManagerFactory.create(context)
and
ReviewManager manager = ReviewManagerFactory.create(this)
are the same thing, the second section on the documentation includes the first one, assuming that "this" is an object of type "context".
Then the line:
val flow = manager.launchReviewFlow(activity, reviewInfo)
might be confusing too, it means that the first parameter of launchReviewFlow should be of type activity, and will not work if you copy and paste the code. You will have to use something like:
val flow = manager.launchReviewFlow(this, reviewInfo)
//or
val flow = manager.launchReviewFlow(this#MainActivity, reviewInfo)
when "MainActivity" is the class name of your activity.
Then to store a counter you can use SharedPreferences or the new DataStore library as dominicoder said.

Why are they advising creating 2 instances of ReviewManger? Firstly with (context) and then with (this)? If both are needed do they both go in the same function or does
ReviewManager manager = ReviewManagerFactory.create(context)
Go somewhere else??
Your post is lacking ... context (hehe) ... but I don't think they're advising creating two instances, you might be looking at two different examples. I can't tell since you posted random snippets instead of a full example.
In any case, you need to create an instance of the manager with a Context. In the first example it would seem the code is called from a View or a Fragment neither of which are themselves Contexts, but they do have a context property.
In the second example, it would seem the code is called from an Activity, which is a Context, so it can be called with this.
As to where this goes - well, it would go wherever you need this manager object.
It would be easier to answer your question with full code examples or links to whatever tutorial you're following.
They also seem to attach listeners etc (in various ways), but as far
as I can see that isn't necessarily needed unless I wish to track
whether the review flow completed or failed etc.
Please correct me if I'm missing anything here.
Is there a question here? Which listeners is it that "they seem to attach"? What is this assumption based on? Can you post some code or a link to an example you're looking at?
Finally, I wish to call this function only when a user has opened a
particular activity 'x' number of times in the app's lifetime. I my
iOS I used User Defaults and reviewWorthyActionCount. Is there
anything similar in Android?
You are asking Android developers if there's anything similar to some iOS constructs without explaining what those constructs are. I could tell you if there is anything similar on Android if you explain what "User Defaults" and "reviewWorthyActionCount" are.
Beyond that, if I wanted to track some simple persistent state like "viewed Activity X times" I'd probably use SharedPreferences or the new DataStore library.

Related

What does "activity?." mean in Kotlin?

I am trying to use a code snippet from the official android documentation (https://developer.android.com/training/printing/photos#kotlin) which is the doPhotoPrint() function in the code that I have attached, in order to learn how to use the PrintHelper class in Kotlin and Android Studio. See the attached image of the the code snippet:
The problem is that when I put the code in Main Activity in my test app, it keeps showing "activity?." as red. Why is this the case and how can I get the code to work so that is provides the user with the option to print? Thanks
The code you linked is just a general how to use a library function snippet - it's not put in any context, but we can assume it's probably written with a Fragment in mind. Fragments have a getActivity() method that returns the Activity it's currently attached to (or null if it's not)
Kotlin allows you to access getter and setter functions from Java code as if they were properties - so basically, instead of having to do value = getThing()
and setThing(newValue) you can treat it like a variable: value = thing, thing = newValue etc. So when you access the activity property in this code, it's really calling getActivity(). And because the result can be null, you use the ? to null-check it before trying to do anything with it.
Really what that snippet is saying is, this code needs access to a Context, and it's using an Activity as one in the example. If you have a Fragment, you can just drop this code right in. If you're in an Activity though, then you obviously don't need to get one - this is your Activity! And you don't need to null-check it either of course. (And there's no activity property or getActivity() method to access, which is why it's showing up red and unrecognised)
So you can just replace that stuff and the checking code around it with:
private fun doPhotoPrint() {
// using 'this' to pass the current activity as the context
val printHelper = PrintHelper(this).apply {
scaleMode = PrintHelper.SCALE_MODE_FIT
}
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.droids)
printHelper.printBitmap("droids.jpg - test print", bitmap)
}
It is showing red because you have not declared the nullable object activity anyway before you use it

Is necessary to create an LiveDataObserver only for one action like this.?

I have the next segment of code, I would like to add unit test for the next button, one friend say "There is too much to create one live data observer only to test each function" . The problem is that I have at least 10 buttons with deferents actions like intents, calculation, send data, etc. each button do different things, some represents actions, others represent events .
The image is only one of those. but the question is, how to make it testeable , I understand for testing I would need to create a method on the viewModel , and make a LiveData only to return to the view and register the observer in the fragment only to make the intent, for me it sounds like "walk around" . Why should I have an observer only for a Intent, if the view dont needs to "observe" nothing, only to notify to the view to do something.
Its similar for the other 10 buttons. Really I need to create one liveData to be able to test each button ? To have one observer and return immediately to the view only to do those things sounds exaggerated . There is a better way to do it ?
buttonContainer.setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri: Uri = Uri.fromParts(SCHEME, activity?.packageName, null)
intent.data = uri
startActivity(intent)
}
// Each of those make one action, but to have an observer for each, don't sounds good,
// why whould I need an observer only for one action , but if don't, I couldn't test it
liveDataAction1.value = true
liveDataAction2.value = true
liveDataAction3.value = true
liveDataAction4.value = true
liveDataAction5.value = true
Instead of having multiple LiveDatas you could have one LiveData<Consumable<Event>>.
Consumable is just a pattern for making sure your events are consumed only once (Read this for more information)
Then define your Event as:
sealed class Event {
class OpenDetail(id: Long): Event
object OnSuccess(): Event
....
}
Then in your tests you just verify that calling method x, results in the according Event to be sent out.

How to ensure a global "cache" for the current session of the user even if process is killed

I will try to be as clear and precise as possible in my situation.
Currently developing an Android application with Koltin based on the MVVM pattern, I'm facing a problem that questions my architecture.
My application consists in retrieving the different orders of a user, each order having itself a list of products.
I had imagined setting up a "CommandController" which is actually a singleton.
object CommandController : ICommandController {
override var m_commandRepository: ICommandRepository = CommandRepository()
override var m_commandList: MutableList<Command> = mutableListOf<Command>()
override var m_currentCommand: Command? = null
override var m_currentProduct : Produit? = null
override var m_currentProductIndex : Int = first_index
}
The purpose of this singleton is to act as an intermediary between my viewModels and my repository in order to store the commands received and the currently active command (only 1 active command at a time).
class CommandListViewModel : ViewModel() {
fun fetchCommandList(refreshStrategy: CommandRefreshStrategy){
viewModelScope.launch {
mProgressBar.postValue(true)
mErrorStatus.postValue(null)
try {
mCommandList.postValue(CommandController.getCommandsList(refreshStrategy)) //USE CONTROLLER HERE
mCommandActive.postValue(CommandController.getCurrentCommand()) //USE CONTROLLER HERE
}
catch (e: Exception) {
//this is generic exception handling
//so inform user that something went wrong
mErrorStatus.postValue(ApiResponseError.DEFAULT)
}
finally {
mProgressBar.postValue(false)
}
}
}
}
Note that no element has a reference to the singleton, kotlin initializing it when it is first needed
If I click on the active command in the list, I display its details
I chose to do this to avoid having to remake queries every time I need to get the list of commands, no matter the fragment / activity, even if for the moment I only use it in 1 place.
So my layers are as follows:
A problem I wasn't aware of is annoying.
Indeed, if I change the permissions granted by the application and I come back in the application, the last activity launched is recreated at the last visited fragment.
The problem is that my singleton comes back to its initial state and so my active command is null because the process was killed by the system after the change of permissions.
So I would like to know if there is a way to persist/recover the state of my singleton when I come back into the application.
I've already tried to transfer my singleton to a class inherited from Application, but that doesn't solve the problem of the killed process.
I've read a lot of articles/subjects about shared preferences but I have a lot of problems with it:
The controller is supposed to be a purely business element, except shared preferences need the context
I don't want the current command and the list of commands to remain if the user kills the application himself ( is there a way to differentiate between the death of the process by the system and by the user ??).
Android will kill off OS processes for all kinds of reasons. If you want your data to survive this then you must store it in a persistent store. One of the following:
Store it in a file
Store it in SharedPreferences
Store it in an SQLite database
Store it in the cloud on some server where you can retrieve it later

Why addSnapshotListener is called twice on firestore CollectionReference? [duplicate]

My firestore onSnapshot() function is being called twice.
let user = firebase.firestore().collection('users').doc(userID).onSnapshot
({
next: (documentSnapshot: firebase.firestore.DocumentSnapshot) =>
{
this.userArray.push(documentSnapshot as User);
console.log(documentSnapshot);
//here
},
error: (firestoreError: firebase.firestore.FirestoreError) =>
{
console.log(firestoreError);
//here
}
});
I have also tried subscribing like in https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener by including user() at the //here comment but to no avail.
How can I modify such that the function only executes one time, i.e. push only one user object per time instead of twice.
I don't know if this is related to your question. If one is using
firebase.firestore.FieldValue.serverTimestamp()
to give a document a timestamp, then onSnaphot will fire twice. This seem to be because when you add a new document to your database onSnapshot will fire, but the serverTimestamp has not run yet. After a few milliseconds serverTimestamp will run and update you document => onSnapshot will fire again.
I would like to add a small delay before onSnapshot fires (say 0,5s or so), but I couldn't find the way to do this.
You can also make a server side function for onCreate event, I believe that would solve your problem. Maybe your userArray.push-action would be more suitable to execute in server side.
Update: To learn more about the behavior of serverTimestamp() and why it triggers the listener twice read this article: The secrets of Firestore’s FieldValue.serverTimestamp() — REVEALED!. Also, the official documentation states:
When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
In the article there are a couple of suggested solutions, one of which is to use the metadata property of the snapshot to find whether the Boolean value of metadata.hasPendingWrites is true (which tells you that the snapshot you’re looking at hasn’t been written to the server yet) or false.
For example, in your case you can check whether hasPendingWrites is false and then push the object:
if ( !documentSnapshot.metadata.hasPendingWrites ){
// This code will only execute once the data has been written to the server
this.userArray.push(documentSnapshot as User);
console.log(documentSnapshot);
}
In a more generic example, the code will look like this:
firestore.collection("MyCollection")
.onSnapshot( snapshot => {
if ( snapshot.metadata.hasPendingWrites ){
// Local changes have not yet been written to the backend
} else {
// Changes have been written to the backend
}
});
Another useful approach, found in the documentation is the following:
If you just want to know when your write has completed, you can listen to the completion callback rather than using hasPendingWrites. In JavaScript, use the Promise returned from your write operation by attaching a .then() callback.
I hope these resources and the various approaches will help anyone trying to figure out a solution.
REFERENCES:
Events for local changes
The hasPendingWrites metadata property
Snapshot Listen Options
If you need a one time response, use the .get() method for a promise.
firebase.firestore().collection('users').doc(userID).get().then(snap => {
this.userArray = [...this.userArray, snap.doc);
});
However, I suggest using AngularFire (totally biased since I maintain the library). It makes handling common Angular + Firebase tasks much easier.

How do you call a method within the same activity

I've been fiddling around with sample codes and ran across a snippet and tried using it but problem is I don't know how to call this kind of method from the same activity I declared it in. The snippet only showed this kind of method and not how to call it. I don't even know what this is defined as so it's been hard finding the answer, a method with multiple parameters I guess?
This is the method I want to call, it's linked to another class Payments.
void Calculate(Context con, Payments Pay)
I've tried the usual Calculate() but it tells me that Calculate(Context,Payments) cannot be applied to ();
Can anyone explain what's going on ?
Generate an object of the class Payments:
Payments pay = New Payment(Paramters);
Then call calculate with the getApplicationContext and the Payment object:
Calculate(getApplicationContext(), pay);
I hope it helps.
you have to pass context means activity instance & instance of payment class
Like
Context con = getActivity();
Payments Pay = new Payments();
Calculate(con,pay);
//removed the "void"
I hope this help you

Categories

Resources