I created a Roll dice app following the Google introduction course to Kotlin.
I am now implementing Firebase Analytics to track each dice rolled.
I followed Firebase instructions to install Firebase SDK within my gradle files.
I followed Google instruction to implement event tracking
I entered the 3 adb commands to see my log events in the Android Studio Logcat tab
But, I don´t know why my events aren't logged, they don't appear in my Logcat tab... When I initialize my app I can see various Firebase logs, but then when I click the button for which I have an event, Firebase doesn't log it. I checked my code and I don´t think problem comes from here.
Someone to help me?
I share with you my code and all messages I have in my terminal and catlog tabs.
package com.example.rolldiceapp
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.FirebaseAnalytics.Param.*
import com.google.firebase.analytics.ktx.logEvent
/*** This activity allows the user to roll a dice and view the result on the screen.***/
class MainActivity : AppCompatActivity() {
private lateinit var firebaseAnalytics: FirebaseAnalytics //Declare the FirebaseAnalytics object at the top of the activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) //Set layout with the Activity
firebaseAnalytics = FirebaseAnalytics.getInstance(this) //Initialize Firebase Analytics in the OnCreate method
val rollButton: Button = findViewById(R.id.roll_button)
rollButton.setOnClickListener {
rollDice()
trackClicks()
}
}
private fun rollDice() {
// Create new Dice object with 6 sides and roll it
val dice = Dice(6)
val diceRoll = dice.rollDice()
// Update the screen with the dice roll number
val resultTextView: TextView = findViewById(R.id.roll_textView)
resultTextView.text = diceRoll.toString()
// Update the screen with the dice roll image
val diceImage: ImageView = findViewById(R.id.roll_imageView)
diceImage.setImageResource(R.drawable.dice_2)
when (diceRoll) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
3 -> diceImage.setImageResource(R.drawable.dice_3)
4 -> diceImage.setImageResource(R.drawable.dice_4)
5 -> diceImage.setImageResource(R.drawable.dice_5)
6 -> diceImage.setImageResource(R.drawable.dice_6)
}
//Update the screen with result message
val luckyNumber = 4
val resultMessage: TextView = findViewById(R.id.resultRollText)
if (diceRoll == luckyNumber) {
resultMessage.text = ("You win! You rolled $diceRoll and it is the lucky number!").toString()
} else {
resultMessage.text = ("Sorry you rolled a $diceRoll and you need a $luckyNumber. Try again!").toString()
}
}
private fun trackClicks() {
firebaseAnalytics.logEvent("Click_Dice_track_2") {
param(SCREEN_NAME, "Dice_Homepage") // send predefined parameters
param(SCORE, value = "test")
param(SOURCE, "Local_Machine")
}
}
}
class Dice(val numSides: Int) {
fun rollDice(): Int {
return (1..numSides).random()
}
}
I share with you the messages I have in the Terminal and the Logcat tab when I initialize my application with Firebase.
You can try to Enabling debug mode
To enable Analytics Debug mode on an Android device, execute the following commands (do not forget to add package name) to terminal:
adb shell setprop debug.firebase.analytics.app com.example.rolldiceapp
If you have more than one emulator running, you have to use adb -s SERIAL (SERIAL for the unique id of the emulator). If one device and one emulator are connected you can use shortcuts: adb -d ... for device and adb -e ... for emulator.
After performing steps from Enabling debug mode, make sure that date and time on your debug device or emulator and on your PC is correct.
If after correcting the date and time events are still not showing on DebugView, clear the app storage. Then restart the app and try again.
Also make sure that you have the latest Google Play Services installed in the device/emulator or nothing is guaranteed to work.
If you go to the settings on your emulator, there is Update button for that. Unfortunately it requires you to sign-in via your Google account.
You can also study this question. Seems that your problem similar to this questions. So examine all answers, there are very useful
Related
I'm trying to write a fitness companion watch app that would collect heart rate, and calories via HealthServices API, and send them to the device, where we display a workout. I've been following suggested examples:
https://github.com/android/wear-os-samples/tree/main/AlwaysOnKotlin, https://github.com/android/health-samples/tree/2220ea6611770b56350d26502faefc28791f3cbd/health-services/ExerciseSample, and https://github.com/googlecodelabs/ongoing-activity .
I'm trying to achieve the following workflow:
Launch app on Wear device when X happens on the phone
Start exercise client on Wear
Send heart rate/calories update on a regular basis back to phone
Show summary screen, and stop exercise client when Y happens on the phone.
All of these work somewhat well until the watch goes into ambient mode. Then I run into the following problems:
When watch is in ambient mode, the capabilities client on the phone cannot locate watch, and tell it to start exercise. Nor can it tell it to stop exercise. What is a suggested workaround for this?
I use message client on phone to send message to the wearable. But nothing happens here, since the current node is empty.
currentNode?.also { nodeId ->
val sendTask: Task<*>? =
messageClient
?.sendMessage(nodeId, WORKOUT_STATUS_MESSAGE_PATH, "START.toByteArray())
When trying to simulate ambient mode by pressing 'hand' on the watch simulator, the ambient mode listener does not actually trigger to tell me the right thing. The screen gets "stuck" instead of updating to what I want it to.
Code for the ambient mode in MainActivity (I'm still learning Compose, so right now Main activity is where it's at, to eliminate other Compose specific errors):
In Manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
In Main Activity:
class MainActivity : FragmentActivity(), AmbientModeSupport.AmbientCallbackProvider {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ambientController = AmbientModeSupport.attach(this)
setContent {
val ambientEvent by mainViewModel.ambientEventFlow.collectAsState()
StatsScreen(ambientEvent)
}
...
}
override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = AmbientModeCallback()
inner class AmbientModeCallback : AmbientModeSupport.AmbientCallback() {
override fun onEnterAmbient(ambientDetails: Bundle) {
Timber.e("ambient event: enter: $ambientDetails")
mainViewModel.sendAmbientEvent(AmbientEvent.Enter(ambientDetails))
}
override fun onExitAmbient() {
Timber.e("ambient event: exit")
mainViewModel.sendAmbientEvent(AmbientEvent.Exit)
}
override fun onUpdateAmbient() {
Timber.e("ambient event: update")
mainViewModel.sendAmbientEvent(AmbientEvent.Update)
}
}
I don't see anything printed in this callback, and then consequently, by StateScreen doesn't really do anything when the device enters in the ambient mode.
created a simple test class to check the statistics of a TO-DO List app.
refer - https://learn.udacity.com/courses/ud940/lessons/d6e700ab-b5d9-4b7b-8bab-64ab79c88f87/concepts/e4e37d5c-2b18-403d-83d9-1646949396f4
the IDE shows no tasks available even though tasks have been added!
new to testing please help me out.
class StatisticsUtilsTest{
#Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero(){
// Create an active tasks (the false makes this active)
val tasks= listOf<Task>(
Task("title","description", isCompleted = false)
)
// Call our function
val result= getActiveAndCompletedStats(tasks)
// Check the result
assertEquals(0f,result.completedTasksPercent)
assertEquals(100f,result.activeTasksPercent)
}
reference error image
I'm developing a Flutter app, using VS Code. I've added some logging to my FlutterActivity, as follows:
package com.example.package
import android.os.Bundle
import android.util.Log
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
val TAG = this.javaClass.name
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Hello World!")
}
//...
When I run my app, I expect to see "Hello World!" displayed in the VS Code Debug Console, along with all of the other Android logging. However, I'm not seeing anything. What am I doing wrong?
Updates:
In Android Studio, the logging appears correctly in the Logcat tab.
With VS Code, if I start debugging my app with the tablet locked, and then unlock it, the logging does appear.
If I use adb logcat, the logging does/does not appear depending on which --regex filter I use, even though the regex matches the logs I'm expecting.
I am trying to set up Google Play Licencing for an app in Android studio for an app written in Kotlin. My goal is to avoid users sharing APK files without purchasing my app through the store.
What I've tried:
I've tried following through their documentation. It's not very useful. It skips over many details and it's not really a tutorial. I couldn't use it.
I've seen this question, which does have a long and detailed tutorial-like answer. But the answer seems long-outdated. It causes lots of warnings and terminates with an exception "Intent must be explicit".
My question in summary is:
How can I set up license checking through Google so people who haven't purchased the app through the store can't install it. This seems to be a very common thing to do even though I couldn't manage to find much of any proper answer around.
Here's how I got it working in 2020:
Open Android Studio.
Click Tools -> SDK Manager
Switch to the SDK Tools tab
Make sure Google Play Licensing Library is installed. If it's not installed, click the checkmark and click Apply.
Up in that screen you can see Android SDK Location. Copy that path:
Click File -> New -> Import Module...:
Paste the path you copied and click the small folder icon on the right of the text-input line:
Click Android\Sdk\extras\google\market_licensing\library and click OK:
Click Next:
Leave everything checked and click Finish:
Now you should have a library folder in your project:
Right click on app and click Open Module Settings:
Click Dependencies:
Click the plus button and choose 3 Module Dependency:
Check library and click OK:
Click OK again and wait for it to sync.
If you get an error
The minSdk version should not be declared in the android manifest file. You can move the version from the manifest to the defaultConfig in the build.gradle file.
Go to library > manifests > AndroidManifest.xml and remove the line <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="15" />.
Go to Gradle Scripts > build.gradle (Module: library):
Change minSdkVersion to 4 and also change compileSdkVersion, buildToolsVersion and targetSdkVersion as necessary, then click Sync Now:
Now that the library is ready, we need the actual implementation of the license checking. Go to MainActivity.kt.
You need to find your Base 64 public key and also generate a salt as shown in this answer. I am going to quote the necessary part of that answer but translate the code to Kotlin:
1.1 Your Base64 unique application key
How to get it:
a. Go to your developer console. Link.
b. If you haven't already created an application draft for your app, do it now.
c. Once you have created the draft, it is a good idea to upload your
.apk as Alpha or Beta. Leave it unpublished.
d. Click Services & APIs
e. Scroll down and find YOUR LICENSE KEY FOR THIS APPLICATION
f. Copy the key into your app like this:
private const val BASE64_PUBLIC_KEY = "YOUR LICENSE KEY FOR THIS APPLICATION";
Make sure that there are no spaces.
1.2 A salt
a. What is a salt?
A salt is random data that is additional input when hashing a
password. They are used to defend against dictionary attacks and
rainbow table attacks.
b. How do I get one?
This is a good link to generate a random salt. There should be exactly
20 random integers, so put 20 in for the amount of random strings to
generate, each string should be 2 characters long (used for this
example, it doesn't have to be). Check numeric digits, and check
Identical strings are allowed. They can be negative numbers too. Try
to remove any redundancy, e.g. 00 -> 0, for the sake of consistency.
c. Where do I put the salt?
When declaring variables just put this code in, except with your
random salt.
private val SALT = byteArrayOf(YOUR RANDOM SALT COMMA SEPARATED 20 INTEGERS)
Variables in step 21 should be added to your main activity class. Now, you should add some code to your main activity. Here's what it should roughly look like (Pay attention to // TODO comments):
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.vending.licensing.*
import kotlin.system.exitProcess
class MainActivity : AppCompatActivity()
{
companion object
{
private const val BASE64_PUBLIC_KEY = "YOUR LICENSE KEY FOR THIS APPLICATION" // TODO replace with your own key
private val SALT = byteArrayOf(YOUR RANDOM SALT COMMA SEPARATED 20 INTEGERS) // TODO replace with your own salt
}
private val deviceId: String by lazy {
Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
}
private lateinit var licenseCheckerCallback: LicenseCheckerCallback
private lateinit var checker: LicenseChecker
private fun doCheck()
{
checker.checkAccess(licenseCheckerCallback)
}
override fun onDestroy()
{
super.onDestroy()
checker.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
// Construct the LicenseCheckerCallback. The library calls this when done.
licenseCheckerCallback = MyLicenseCheckerCallback()
// Construct the LicenseChecker with a Policy.
checker = LicenseChecker(
this,
ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
)
doCheck()
setContentView(R.layout.activity_main) // TODO Replace with your own layout
}
private fun displayResult(result: String)
{
// TODO you can change this how the info is displayed
Toast.makeText(this, result, Toast.LENGTH_SHORT).show()
}
private inner class MyLicenseCheckerCallback : LicenseCheckerCallback
{
override fun allow(reason: Int)
{
if (isFinishing)
{
// Don't update UI if Activity is finishing.
return
}
// Should allow user access.
}
override fun applicationError(errorCode: Int)
{
// TODO handle the error your own way. Calling `dontAllow` is common.
dontAllow(Policy.NOT_LICENSED)
}
override fun dontAllow(reason: Int)
{
if (isFinishing)
{
// Don't update UI if Activity is finishing.
return
}
if (reason == Policy.RETRY)
{
// If the reason received from the policy is RETRY, it was probably
// due to a loss of connection with the service, so we should give the
// user a chance to retry. So show a dialog to retry.
// TODO handle Policy.RETRY
}
else
{
// Otherwise, the user isn't licensed to use this app.
// Your response should always inform the user that the application
// isn't licensed, but your behavior at that point can vary. You might
// provide the user a limited access version of your app or you can
// take them to Google Play to purchase the app.
// TODO implement goto market
}
displayResult("Not Licensed")
// TODO you may not abort if you have some other way to handle the fail case
abort()
}
}
private fun abort()
{
finishAffinity()
exitProcess(0)
}
}
Add these permissions to your manifest file:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/>
If you get an exception with a message similar to:
Service Intent must be explicit: Intent { act=com.android.vending.licensing.ILicensingService }
Apply the fix in this answer.
That should be all. See the answer I quoted previously for more info. I hope this saves others some time.
I would like my app tз display a notification of the incoming messages, but only when the application is active, similar to how many social apps do that.
I.e., the user has my messaging app open and he gets a notification slide in from top, within this Android application.
To my understanding, this is something that is called “in app messages” in Firebase.
However, I wouldn’t like to have firebase as a dependency, as I am not using any part of it: the notifications will be triggered by an open network connection that my app made.
I also so wouldn’t want to involve push notifications as I need this functionality only when the app is active.
What would be the best way to achieve this goal?
Basically what I am asking is how to make my own notification “bubble” in UI that shows up inside my app, similar to how it is done in messaging/dating apps (see Badoo, for example). Mainly I am wondering if there are any implemendations available that I could use or do I have to draw this stuff myself (using Fragments?)
It's a very broad question. So in broad strokes: Use some real time communications technology, such as sockets/websockets to listen for incoming messages, and hook up into lifecycle to start listening when the app moves into foreground (and stop when it moves out) [assuming that is the meaning of app being active - otherwise if you include foreground state, just start listening and don't unlisten) -
class MyListener : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
listenForNotification()
//start listening
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
//stop listening
}
where listener would be something like this:
private suspend fun listenForNotification(){
withContext(Dispatchers.IO){
myApi.receive() {
println("this is my notification object: $it")
NotificationHelper.sendNotification($it.message)
}
}
}
And NotificationHelper would be based on Notification Manager to push local notifications (as you wanted them to slide from the top - look like any push notification). Pay close attention to the flags you use to send the notification to make sure it is received and processed by the currently opened activity (do more research on it, separate topic) https://developer.android.com/reference/android/app/NotificationManager
In that same activity use OnNewIntent to receive user's action of tapping on the notification and then do whatever you want to do with it.
Alternatively, do not use local notification but just develop your own UI where you would display these things messaging style. (edit: for example, like this - link. Another one for actually showing notifications without using Notifications lib -link
Or a combination of both local notifications and the above example.
Edit:
*You can also use Firebase messaging to display messages locally.* Yes you would still need a firebase json to init the app, but after that you can construct your messages locally and display them, so it a very lightweight dependency on two libs and aside from initializing you won't need anything else from the firebase server.
Below is an example with two types of messages, card and banner. And of course you can just take the full code on GitHub and extract the part you need and modify it as needed. (the method used here is public for testing the appearance of the message locally - I don't see anything wrong with using it as a vehicle to deliver local notifications, but again the option to take the code and modify is always there)
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.firebase.FirebaseApp
import com.google.firebase.inappmessaging.MessagesProto
import com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay
import com.google.firebase.inappmessaging.model.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
companion object {
val whiteHex = "#ffffff"
val magHex = "#9C27B0"
val appUrl ="app://open.my.app"
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val action: String? = intent?.action
val data: Uri? = intent?.data
data?.let {
helloTextView.text ="You just clicked from Firebase Message"
return
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
FirebaseApp.initializeApp(this)
val text = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Body")
.build()
val title = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Title")
.build()
val imageData = ImageData.builder()
.setImageUrl("https://homepages.cae.wisc.edu/~ece533/images/frymire.png")
.build()
val button = Button.builder()
.setButtonHexColor(whiteHex).setText(text).build()
val campaignMeta = CampaignMetadata("S", "D", true)
val primaryAction = Action.builder()
.setActionUrl(appUrl)
.setButton(button)
.build()
val fmessage = CardMessage.builder()
.setPrimaryAction(primaryAction)
.setBackgroundHexColor(magHex)
.setPortraitImageData(imageData)
.setTitle(title).build(campaignMeta)
val bannerMessage = BannerMessage.builder()
.setAction(primaryAction)
.setImageData(imageData)
.setBackgroundHexColor(magHex)
.setBody(text)
.setTitle(title).build(campaignMeta)
FirebaseInAppMessagingDisplay
.getInstance()
.testMessage(this, bannerMessage, null)
}
}
In build.gradle make sure to add:
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-inappmessaging-display:19.0.2'
and intent filter into manifest (to process click on the message)
<data android:scheme="app" android:host="open.my.app" />
also modify launchMode to singleTop to process the click within the same instance of the activity:
<activity android:name=".MainActivity"
android:launchMode="singleTop"
>
and apply
apply plugin: 'com.google.gms.google-services'
The result:
Card message:
Banner message and updating text in response to clicking on the banner:
Added project into GitHub if you are interested - project link. Must add your own google-services.json for firebase (to be able to init the engine only)