How can I randomly reward points for app usage - java

I am working on an android app where user get points for using the app which can be used to unlock in-app features.
I have a function called rewardPoints() which generates random integer and I want it to get called randomly while the user is using the app. The points then gets added up in database.
fun rewardPoints() {
var points = Random().nextInt((5-1) + 1)
}
How do I call the function rewardPoints() randomly while the user is using/interacting with the app?

I'd use a Handler to post a Runnable that re-posts itself. Like so,
val handler = Handler()
handler.post({
rewardPoints()
handler.postDelayed(this, DELAY_TIME_MS)
})
You could kick this off in your Activity's onResume and stop it onPause to make sure it's only running when the app is active.

You could add an observer on your activities, check whether you have active activities and when that's the case start a periodic task to award points.
Sample:
class MyApp : Application(), Application.ActivityLifecycleCallbacks {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(this)
}
var count: Int by Delegates.observable(0) { _, old, newValue ->
when (newValue) {
0 -> onBackground()
1 -> if (old == 0) onForeground()
}
}
override fun onActivityResumed(activity: Activity?) {
count++
}
override fun onActivityPaused(activity: Activity?) {
count--
}
fun onForeground() {
Log.d("TAG", "start.")
events.start()
}
fun onBackground() {
Log.d("TAG", "stop.")
events.cancel()
}
val events = object: CountDownTimer(Long.MAX_VALUE, 1000) {
// is called once per second as long as your app is in foreground
override fun onTick(millisUntilFinished: Long) {
if (ThreadLocalRandom.current().nextInt(100) < 5) {
Toast.makeText(this#MyApp, "You earned a point.", Toast.LENGTH_SHORT).show()
}
}
override fun onFinish() { /* will never happen */}
}
/* not needed */
override fun onActivityStarted(activity: Activity?) {}
override fun onActivityDestroyed(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
override fun onActivityStopped(activity: Activity?) {}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {}
}
If you use architecture components Lifecycle, implementing above is even simpler with https://developer.android.com/reference/android/arch/lifecycle/ProcessLifecycleOwner and listening to the desired Lifecycle.Event

Related

Android pass Edit Text data onChange from Activity to Fragment

I have a main activity with a heading and a search field (edit text), I want to be able to search and the results are immediately shown in the fragment, like an onChange instead of waiting for the user to click a button to filter results. (which is in the activity).
I can get it working if I include the Edit Text in my fragment too, but I don't want it that way for design purposes, I'd like to retrieve the user values as they are typed from the activity, and get them in my fragment to filter results
I've tried Bundles but could not get it working, and also not sure If i could use Bundles to get the results as they are being input.
Here's a screenshot to help understand better
You can make it happen using ViewModel + MVVM architecture.
MainActivity:
binding.editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
viewModel.updateSearchText(s)
}
})
ViewModel:
private val _searchText = MutableLiveData<Editable?>()
val searchText: LiveData<Editable?> get() = _searchText
fun updateSearchText(text: Editable?) {
_searchText.value = s
}
Fragment:
searchText.observe(viewLifecycleOwner) {
// TODO: handle the searched query using [it] keyword.
}
If you don't know what View Model is or how to implement it, use the official Google tutorial: https://developer.android.com/codelabs/basic-android-kotlin-training-viewmodel
Another way to achieve this (besides using an Android ViewModel) is use the Fragment Result API.
For instance, if you place the EditText into a fragment (let's call it QueryFragment), you can get the result of the QueryFragment in your SearchResults fragment like so:
// In QueryFragment
editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(s: Editable?) {
setFragmentResult("searchQueryRequestKey", bundleOf("searchQuery" to s.toString()))
}
})
// In SearchResultsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve the searched string from QueryFragment
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("searchQueryRequestKey") { requestKey, bundle ->
val searchQuery = bundle.getString("searchQuery")
// Perform the search using the searchQuery and display the search results
}
}

Multiple Beacon Consumer and beacon is not detecting using AltBeacon

I am using altbeacon library in one of my android projects. In that project BeaconConsumer is already implemented in one of the activities. When I try to implement beacon detection in my part of the code beacon is not detecting.
But when i remove the BeaconConsumer code from the previously written code, then my code works and detects beacon.
This is the log when the beacon is not detecting. Why are there multiple consumers and is it the problem? If so, how can I remove multiple consumers.
I/System.out: ---------onBeaconServiceConnect
D/BeaconParser: Parsing beacon layout: m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25
D/BeaconParser: Parsing beacon layout: x,s:0-1=feaa,m:2-2=20,d:3-3,d:4-5,d:6-7,d:8-11,d:12-15
D/BeaconParser: Parsing beacon layout: s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19
D/BeaconParser: Parsing beacon layout: s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v
D/BeaconParser: Parsing beacon layout: m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24
D/BeaconLocalBroadcastProcessor: Register calls: global=6 instance=6
D/BeaconLocalBroadcastProcessor: Register calls: global=7 instance=7
D/BeaconManager: consumer count is now: 2
D/BeaconManager: This consumer is not bound. Binding now: MainActivity#11b9235
D/BeaconManager: Not starting beacon scanning service. Using scheduled jobs
D/BeaconLocalBroadcastProcessor: Register calls: global=8 instance=8
D/BeaconManager: consumer count is now: 3
D/BeaconManager: Unbinding
D/BeaconManager: Not unbinding from scanning service as we are using scan jobs.
D/BeaconManager: Before unbind, consumer count is 3
D/BeaconManager: After unbind, consumer count is 2
This is the class i am using
class BeaconsDataSource(private val context: Context) : BeaconConsumer, RangeNotifier,MonitorNotifier {
private val beaconManager: BeaconManager =
BeaconManager.getInstanceForApplication(context)
private val region = Region("myRegion", null, null, null)
private lateinit var data: Step
private var listener: ((Step) -> Unit)? = null
override fun getApplicationContext(): Context = context
override fun unbindService(p0: ServiceConnection?) {
applicationContext.unbindService(p0!!)
}
override fun bindService(p0: Intent?, p1: ServiceConnection?, p2: Int): Boolean {
return applicationContext.bindService(p0, p1!!, p2)
}
override fun onBeaconServiceConnect() {
BeaconManager.setDebug(true)
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("x,s:0-1=feaa,m:2-2=20,d:3-3,d:4-5,d:6-7,d:8-11,d:12-15"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"))
beaconManager.beaconParsers.add(BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
beaconManager.removeAllMonitorNotifiers()
beaconManager.removeAllRangeNotifiers()
beaconManager.addMonitorNotifier(this)
beaconManager.addRangeNotifier(this)
try {
beaconManager.startMonitoringBeaconsInRegion(region)
beaconManager.startRangingBeaconsInRegion(region)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun didRangeBeaconsInRegion(p0: MutableCollection<Beacon>, p1: Region?) {
data = Step.ENTER_NUMBER
notifyListeners()
}
private fun notifyListeners() {
listener?.invoke(data)
}
fun requestBeaconsUpdate(listener: (Step) -> Unit) {
this.listener = listener
beaconManager.bind(this)
}
fun stopBeaconsUpdate() {
beaconManager.unbind(this)
}
override fun didDetermineStateForRegion(p0: Int, p1: Region?) {
val msg = "---------did determine state for region $p0"
Log.d("TAG", msg)
}
override fun didEnterRegion(p0: Region?) {
val msg = "-----------did enter region"
val zone = p0.toString()
Log.d("TAG", "----------Enter in region")
val text = "Enter in $zone"
Log.d("TAG", msg)
}
override fun didExitRegion(p0: Region?) {
val msg = "------------did exit region"
Log.d("TAG", msg)
}
}
This is my livedata class which i am calling from viewmodel
class BeaconsLiveData() : LiveData<Step>() {
private val beaconDataSource = BeaconsDataSource(App.getAppContext())
private val listener = { data: Step ->
value = data
}
override fun onActive() {
super.onActive()
beaconDataSource.requestBeaconsUpdate(listener)
}
override fun onInactive() {
super.onInactive()
beaconDataSource.stopBeaconsUpdate()
}
}
It is hard to say what is causing the multiple bind operations but that likely is contributing to your detection problems. I suspect it has something to do with the LiveData lifecycle. Debugging this is unfortunately more involved than possible (for me) looking at the code snippet.
In general you should design your system so it calls beaconManager.bind(...) and .unbind(...) infrequently. Doing so frequently causes problems as it will start and stop services which is a heavy weight mechanism.
I did create a working Kotlin sample app here in case that is helpful to you. This sample demonstrates using MutableLiveData to share beacon detection data from a central class that is doing the detection to your Activity. The central class in this case is an Android application class, but it could just as well be any singleton with a long lifecycle. The key thing is that it calls bind(...) once when it starts up, and won't call it again until you are done working with beacons.

Making app service work invisible in background

i have an app that runs a background service infinitely while also hiding the app icon and hiding it from the backstack so it wont be visible to the user while running using:
val componentName = ComponentName(this, FullscreenActivity::class.java)
p.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
this is how i launch the service from my fragment :
parentFragment?.activity?.startService(
Intent(
requireParentFragment().requireActivity().applicationContext,
RescueService::class.java
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
which successfully hides the icon and the app from backstack and the service keep running, also when i press the square button on my phone to show the backstack apps running i dont see the app there which is what i want, but if i click the "X" button to clear all backstack apps it also kills my app and my service dies even though the app does not appear there as i mentioned.
Any ideas what makes the service die after doing it? because it did not happen to couple of days ago before i did some changes in my app..
One of the changes i made is turnning my Service into LifecycleService because i needed my service to observe a livedata which needs a LifecycleOwner.Can it be the cause of my service to die when clearing the backstack?
All suggestions will be welcomed !
EDIT1 - here is my TestService that performs voice recording uses WorkManager.I call hideApp() to hide the icon from the phone and make the app invisible from the backstack :
class RecordingWork(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override val coroutineContext = Dispatchers.Main
override suspend fun doWork(): Result {
mSpeechRecognizer.startListening(mSpeechRecognizerIntent)
return Result.success()
}
}
private lateinit var mSpeechRecognizer: SpeechRecognizer
private val mSpeechRecognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
class TestService : Service() {
private lateinit var workManager: WorkManager
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("TestService", "onStartCommand called")
hideApp()
prepareVoiceRecording()
workManager = WorkManager.getInstance(applicationContext)
workManager.enqueue(OneTimeWorkRequestBuilder<RecordingWork>().build())
return START_REDELIVER_INTENT
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
fun prepareVoiceRecording() {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(applicationContext)
mSpeechRecognizerIntent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
)
mSpeechRecognizerIntent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE,
Locale.getDefault()
)
mSpeechRecognizer.setRecognitionListener(
object : RecognitionListener {
#RequiresApi(Build.VERSION_CODES.M)
override fun onError(i: Int) {
Log.d(TAG, "onErrorCalled error is $i")
if (i == SpeechRecognizer.ERROR_NETWORK || i == SpeechRecognizer.ERROR_SPEECH_TIMEOUT || i == SpeechRecognizer.ERROR_NO_MATCH) {
Log.d(TAG, "error triggered")
mSpeechRecognizer.destroy()
prepareVoiceRecording()
workManager.enqueue(OneTimeWorkRequestBuilder<RecordingWork>().build())
}
}
#RequiresApi(Build.VERSION_CODES.M)
override fun onResults(bundle: Bundle) {
Log.d(TAG, "onResults Called")
//getting all the matches
val matches = bundle
.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)!!
workManager.enqueue(OneTimeWorkRequestBuilder<RecordingWork>().build())
}
private fun hideApp() {
val componentName = ComponentName(
this,
WelcomeScreenActivity::class.java
)
packageManager.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
this service does work to repeatedly record voice by rescheduling the Worker every time. but this service also dies when clearing backstack (even though the app does not appear there because of hideApp())
Use workManager for long running background task (some device like Mi phone you need to white list the app for battery saver if you don't do that the OS will kill your app) and hiding of app icon not work in android X (only system apps can hide their icon)

notifyItemChanged() not working when called within setOnUtteranceProgressListener

Edit
I've created a demo project on Github showing the exact problem. Git Project.
I've written an expandable recyclerView in Kotlin Every row has a play button which uses TextToSpeech. The text of the play button should change to stop whilst its playing, then change back to play when it finishes.
When I call notifyItemChanged within onStart and onDone of setOnUtteranceProgressListener, onBindViewHolder is not called and the rows in the recyclerView will no longer expand and collapse correctly.
t1 = TextToSpeech(context, TextToSpeech.OnInitListener { status ->
if (status != TextToSpeech.ERROR) {
t1?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {
recyclerView.adapter.notifyItemChanged(position)
}
override fun onStop(utteranceId: String?, interrupted: Boolean) {
super.onStop(utteranceId, interrupted)
onDone(utteranceId)
}
override fun onDone(utteranceId: String?) {
val temp = position
position = -1
recyclerView.adapter.notifyItemChanged(temp)
}
override fun onError(utteranceId: String?) {}
// override fun onError(utteranceId: String?, errorCode: Int) {
// super.onError(utteranceId, errorCode)
// }
})
}
})
onBindViewHolder:
override fun onBindViewHolder(holder: RabbanaDuasViewHolder, position: Int){
if (Values.playingPos == position) {
holder.cmdPlay.text = context.getString(R.string.stop_icon)
}
else {
holder.cmdPlay.text = context.getString(R.string.play_icon)
}
}
How can I call notifyItemChanged(position) from within setOnUtteranceProgressListener or what callback can I use so that notifyItemChanged only executes when it's safe to execute?
I tried to replicate your issue and I came to know that it is not working because methods of UtteranceProgressListener is not called on main thread and that's why onBindViewHolder method of the adapter is not called.
This worked for me and should work for you too:
Use runOnUiThread{} method to perform actions on RecyclerView like this:
t1.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onError(utteranceId: String?) {
}
override fun onStart(utteranceId: String?) {
runOnUiThread {
recyclerView.adapter?.notifyItemChanged(position)
}
}
override fun onStop(utteranceId: String?, interrupted: Boolean) {
super.onStop(utteranceId, interrupted)
onDone(utteranceId)
}
override fun onDone(utteranceId: String?) {
val temp = position
position = -1
runOnUiThread {
recyclerView.adapter?.notifyItemChanged(temp)
}
}
}
I solved the problem using runOnUiThread thanks to Birju Vachhani.
For a full working demo of not just the problem I had, but how to correctly expand and collapse rows in a RecyclerView (no onClick events in onBindViewHolder) see my Gitlab Demo Project.

Android kotlin extension error

java.lang.VerifyError: .../utils/KotlinViewExtKt$animateFadeOut$1
Getting that error while running app on emulator PRE Lolipop (<21 api)
Function causing the trouble:
fun View.animateFadeOut(animDuration: Long = 250) {
this.animate()
.alpha(0F)
.setDuration(animDuration)
.setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {}
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
super.onAnimationEnd(animation, isReverse)
show(false)
}
override fun onAnimationEnd(p0: Animator?) {
show(false)
}
override fun onAnimationCancel(p0: Animator?) {
}
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
}
override fun onAnimationStart(p0: Animator?) {
}
})
.start()
}
fun View.show(show: Boolean) {
val vis = if (show) View.VISIBLE else View.GONE
if (visibility != vis) {
visibility = vis
}
}
Pointing on .setListener line.
Works perfectly on 21+ api.
AS versio: 3.0.1. Kotlin version: 1.2.21 (tried 1.1.51).
What could be the reason? My bad or kotlin?
Multidex is enabled.
Solution
Based on this issue and this fix:
ViewExtension.kt
fun View.animateFadeOut(animDuration: Long = 250L) {
this.animate()
.alpha(0F)
.setDuration(animDuration)
.setListener(object : AbstractAnimatorListener() {
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
super.onAnimationEnd(animation, isReverse)
show(false)
}
override fun onAnimationEnd(p0: Animator?) {
show(false)
}
})
.start()
}
fun View.show(show: Boolean) {
val vis = if (show) View.VISIBLE else View.GONE
if (visibility != vis) {
visibility = vis
}
}
AbstractAnimatorListener.kt
abstract class AbstractAnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {}
override fun onAnimationEnd(p0: Animator?) {}
override fun onAnimationCancel(p0: Animator?) {}
override fun onAnimationStart(p0: Animator?) {}
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
onAnimationEnd(animation)
}
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
onAnimationStart(animation)
}
}
Explanation
Try to remove these methods added in API 26 using Java 8 Defaults and use alternative animation:
Animator.AnimatorListener:
onAnimationEnd added in API level 26
void onAnimationEnd (Animator animation, boolean isReverse)
onAnimationStart added in API level 26
void onAnimationStart (Animator animation, boolean isReverse)
These methods can be overridden, though not required, to get the additional play direction info when an animation starts.
AnimatorSet:
Starting in Android 8.0 (API 26) the AnimatorSet API supports
seeking and playing in reverse.
Note:
/**
* Skipping calling super when overriding this method results in
* {#link #onAnimationStart(Animator)} not getting called.
*/
default void onAnimationStart(Animator animation, boolean isReverse) {
onAnimationStart(animation);
}
I need to add this to my build.gradle file to test it
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}
or replace
super.onAnimationEnd(animation, isReverse)
by
onAnimationEnd(animation)
to avoid the error:
Super calls to Java default methods are prohibited in JVM target 1.6.
Recompile with '-jvm-target 1.8'
And add the next line to my gradle.properties file thanks to this answer
android.enableD8=true
to avoid the exception:
com.android.dx.cf.code.SimException: default or static interface
method used without --min-sdk-version >= 24
It compiles now, launching a kitKat emulator just now...
It works, and MultiDex is also enabled in my project.
Sorry, I cannot reproduce it.
Alternatively, remove the two methods using Java 8 defaults, it also works.
Note2:
Arpan suggestion about AnimatorListenerAdapter would work but it's not necessary.
You can remove those methods, change the animation and create:
object EmptyAnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {}
override fun onAnimationEnd(p0: Animator?) {}
override fun onAnimationCancel(p0: Animator?) {}
override fun onAnimationStart(p0: Animator?) {}
}
And use delegation like this:
.setListener(object : Animator.AnimatorListener by EmptyAnimatorListener {
override fun onAnimationStart(animation: Animator) {
// Do something
}
override fun onAnimationEnd(animation: Animator) {
// Do something
}
})
Note3:
Reproduced adding it to an extension function:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: ...android, PID: 3409
java.lang.VerifyError: com/.../ui/common/extension/android/view/ViewExtensionKt$animateFadeOut$1
Removing the Api 26 methods using Java Defaults solves the issue.
Replacing the super call calling your onAnimationEnd also solves it:
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
onAnimationEnd(animation)
}
Calling super.onAnimationStart(animation: Animator?, isReverse: Boolean) requires API 26:
Decompiling Kotlin Bytecode shows these methods cannot be resolved:

Categories