I'm having a hard time getting responses to my questions lately and I'm not why. If I am not asking right, can someone please let me know so I can adjust?
I am building an app with Ionic but having issues sending badge numbers for Android via FCM. I can't find anything in the Firebase Docs. How do I set the badge count when a message is received?
Here is my thought process but I don't know how to get it to work:
when the message is received, use the ionic native badge plugin to set it
send the badge count using FCM payload
NOTE: I am using NodeJS implementation for Firebase
What I've tried
I am using the cordova-plugin-fcm plugin and the Android ShortcutBadger to set the badge, but I am unable to set the badge when the message is received. I was only able to set it when the app is already open by calling the shortcutbadger function in the MyFirebaseMessagingService java class (of the cordova-plugin-fcm). Below is the code I am using in my FCMPluginActivity java class:
// more imports here
import me.leolin.shortcutbadger.ShortcutBadger;
public class FCMPluginActivity extends Activity {
private static String TAG = "FCMPlugin";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "==> FCMPluginActivity onCreate");
Map<String, Object> data = new HashMap<String, Object>();
if (getIntent().getExtras() != null) {
Log.d(TAG, "==> USER TAPPED NOTFICATION");
data.put("wasTapped", true);
for (String key : getIntent().getExtras().keySet()) {
String value = getIntent().getExtras().getString(key);
Log.d(TAG, "\tKey: " + key + " Value: " + value);
data.put(key, value);
}
}
FCMPlugin.sendPushPayload(data);
ShortcutBadger.applyCount(FCMPluginActivity.this, 8);
finish();
forceMainActivityReload();
}
I am importing ShortcutBadger and using it in the onCreate method. The idea is to set the badger when the notification is received; however, this is not working.
Your thoughts and comments are very much appreciated
Set the badge field when you send the notification. The documentation specifies this for ios and based on my test it works, but with some delay. I would also try it for Android, but it may not be possible:
Search for 'badge' here:
https://firebase.google.com/docs/cloud-messaging/http-server-ref
Related
I want to know which comments users are currently reading and replying to on Youtube through Android Accessibility Service.
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if(nodeInfo != null) {
Log.d(TAG, "NodeInfo: " + nodeInfo);
Log.d(TAG, "NodeInfo: " + nodeInfo.getContentDescription());
dfs(nodeInfo);
}
...
}
I tried using event.getSource() and DFS to log all the AccessibilityNodeInfo in onAccessibilityEvent() but I can't get the comment content without enabling the talkback function on the phone.
Once the talkback function of the phone is enabled, the comment content on the phone screen could be logged with the exact same code running.
Did anyone have the same problem as me?
Is there any possibility of NOT enabling the talkback function and also getting the comment content showing on the phone screen?
I've integrated UnityAds on my Android app (that is not published yet).
I get app id and placement id from database on my server.
App id and placement id are correct, I've copied and pasted about 30 times for be sure of it.
So, when I try to get an ad in test mode, it give me the INVALID_ARGUMENT error.
Here an explaination of the error code by Unity, but as you can see it is a little generic.
I have an object that simply represents an ad service (like admob, FAN, inmobi etc)
In this case the object is called advert, and here it's how I show an ad with Unity:
protected void showUnity(){
UnityAds.initialize(this, advert.getApiKey(), true); //advert.getApiKey() returns the app id
UnityAds.addListener(new IUnityAdsListener() {
#Override
public void onUnityAdsReady(String s) {
Log.i(TAG, "onUnityAdsReady "+s);
if(s.equals(advert.getUnitId()) && !unityReady)
UnityAds.show(ActivityAd.this, advert.getUnitId()); //advert.getUnitId() returns the placement id
}
#Override
public void onUnityAdsStart(String s) {
Log.i(TAG, "onUnityAdsStart "+s);
unityReady = true;
}
#Override
public void onUnityAdsFinish(String s, UnityAds.FinishState finishState) {
if (finishState.compareTo(UnityAds.FinishState.COMPLETED) == 0) {
onAdReward(); //my callback for reward
} else if (finishState.compareTo(UnityAds.FinishState.SKIPPED) == 0) {
onAdClosed(); //my callback for ad close
} else if (finishState.compareTo(UnityAds.FinishState.ERROR) == 0) {
onAdError(finishState.toString()); //my callback for errors
}
}
#Override
public void onUnityAdsError(UnityAds.UnityAdsError unityAdsError, String s) {
onAdError(unityAdsError.toString()); //my callback for errors, here results INVALID_ARGUMENT error
}
});
}
Does anyone know what is wrong? Thanks in advance
If you check the callback closely the onUnityAdsError has 2 params, first provides the error code and the second param provides you information about what went wrong.
#Override
public void onUnityAdsError(UnityAds.UnityAdsError unityAdsError, String reason) {
onAdError(unityAdsError.toString()); //my callback for errors, here results INVALID_ARGUMENT error
}
So just check the reason and you should be able to find out what is going wrong in your integration.
Here are some methods which you can follow to solve this INVALID_ARGUMENT problem
1. Make sure you are implementing the right Initialization code in your app. There are 2 types of Initialization.
Only Unity ads Initialization
Mediation Initialization
and both methods have their own banner, interstitial, and rewarded ad code.
2. Make sure you enable test mode as Boolean. (i.e: private Boolean testMode = true;) (make sure to do false this before publish on store)
3. You can add your mobile phone as a test device to get test ads on your phone forcefully. for this, you have to first copy the Ad ID of your device. For that, go to your mobile settings > Google > Ads > This device's advertising ID. copy that ID and go to unity dashboard > Monetization > Testing > Add Test Device. Add your device Ads ID here with any name, and now you will be able to see test ads on the device.
I'm going nuts with this error. As far as I can see I've followed the instructions correctly. My scopes are YOUTUBE_FORCE_SSL. In desperation I've tried to add all Google Plus Scopes without luck. Still get the same error, both in the device, emulator and Google Api Explorer. The video I try to comment on are public. I have a Google+ profile and are signed in with it when I try to make a comment.
This is the full error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "youtube.commentThread",
"location" : "Authorization",
"locationType" : "header",
"message" : "The callers YouTube account is not connected to Google+.",
"reason" : "ineligibleAccount"
} ],
"message" : "The callers YouTube account is not connected to Google+."
}
This is my code:
try {
HashMap<String, String> parameters = new HashMap<>();
parameters.put("part", "snippet");
CommentThread commentThread = new CommentThread();
CommentThreadSnippet snippet = new CommentThreadSnippet();
Comment topLevelComment = new Comment();
CommentSnippet commentSnippet = new CommentSnippet();
commentSnippet.set("textOriginal", textComment);
commentSnippet.set("channelId", channelId);
commentSnippet.set("videoId", ytId);
topLevelComment.setSnippet(commentSnippet);
snippet.setTopLevelComment(topLevelComment);
commentThread.setSnippet(snippet);
YouTube.CommentThreads.Insert commentThreadsInsertRequest = mService.commentThreads().insert(parameters.get("part"), commentThread);
CommentThread response = commentThreadsInsertRequest.execute();
Log.i("COMMENT:", response.toString());
}
Adding screenshot from Api Explorer:
Can you get CommentThreads: insert to work with the API Explorer? If so, how?
I have seen the answers to a similar question here and they don't solve this problem.
Any help is appreciated.
Edit 1
After further testing. Everything works fine with an old account I have. I've tried to see which settings could be different, so far without luck.
This also works if I switch to a YouTube brand account.
The problem remains, it don't work for all Google Accounts, not even when they're also Google+ accounts. The error seems to imply that the request is not made from a Google+ Account. Would be great if Google could clarify the exact reason.
Also, is it possible to programmatically make an account eligible to make a comment, after asking the permission from the account owner? How?
Edit 2
According to this page the reason for the error is this:
The YouTube account used to authorize the API request must be merged
with the user's Google account to insert a comment or comment thread.
How can this be done within the app?
Edit 3
I guess the answer can be found here. You're not able to comment without a YouTube Channel.
The problem is that you're not able to comment unless you have a private YouTube Channel or are logged in with your Brand Account. Using the model to login that Google gave in the instructions don't allow login with Brand Accounts, they're not visible in the account picker.
The result is that you're able to login with an account that have YouTube Brand Accounts, but you will not be able to comment using that accountand since you're not able to pick a Brand Account there is no way to solve this unless you ask users to also create a private Channel. The error message should say something like this:
The callers YouTube account is not a YouTube Channel Account.
If you have created a new account and if you haven't commented on any of the youtube videos, then it throws the error: "The callers YouTube account is not connected to Google+."
Solution: Atleast manually comment in any of the youtube videos using the new account and then try the API. It works smoothly.
Since you can't post comments without a private YouTube Channel(see edits above) the solution would look something like this. If you can find a better one, please submit!
1) Catch the Error. Give Alert with instructions.
2) Launch a Webview with this URL: https://m.youtube.com/create_channel?chromeless=1&next=/channel_creation_done
3) Monitor the Webview and determine when the URL change to the following: https://m.youtube.com/channel_creation_done. The URL indicates that a channel has been created.
4) Close the Webview and resend the authorized API request.
The URLs above were found here but the error code to catch is not the same as on that page. You should catch a 403 with "reason" : "ineligibleAccount".
Update June 29th, 2018
I got back to this issue today and got it working in an acceptable way. See my implementation below:
1. Catch the error 403 after user posted comment without YouTube Channel
if(mLastError.getMessage().contains("403") && mLastError.getMessage().contains("ineligibleAccount")) {
// Show Alert with instruction
showAlertCreate("Please Create a YouTube Channel!", "You need a personal YouTube Channel linked to your Google Account before you can comment. Don't worry, it's easy to create one!\n\n1) Tap on CREATE below and wait for page to load.\n\n2) Login if needed.\n\n3) Tap CREATE CHANNEL and wait until comment is posted.");
}
Code for Alert:
public void showAlertCreate(String title, String description) {
AlertDialog.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder = new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
} else {
builder = new AlertDialog.Builder(this);
}
builder.setTitle(title)
.setMessage(description)
.setPositiveButton(R.string.yes_create, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Start Youtube WebView to create Channel
Intent intent = new Intent(mContext, WebViewActivity.class);
startActivityForResult(intent, 777);
}
})
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
2. When user tap CREATE in Alert, open this WebView
Notice this code to start Intent in alert above:
// Start Youtube WebView to create Channel
Intent intent = new Intent(mContext, WebViewActivity.class);
startActivityForResult(intent, 777);
XML for WebView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WebViewActivity">
<WebView
android:id="#+id/create_channel"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
Code for WebView:
public class WebViewActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view);
WebView createChannel = findViewById(R.id.create_channel);
createChannel.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
if (url!=null && url.contains("https://m.youtube.com/channel_creation_done")) {
view.setVisibility(View.INVISIBLE);
//Log.i("URLWEB", url);
Intent intent = new Intent();
intent.putExtra("created", "yes");
setResult(RESULT_OK, intent);
finish();
}
}
});
createChannel.loadUrl("https://m.youtube.com/create_channel?chromeless=1&next=/channel_creation_done");
}
}
3. Catch when user completed Create Channel step in your activity
In onActivityResult() include something like this:
if (requestCode == 777) {
if(resultCode == RESULT_OK) {
// Receive intent from WebView, if new Channel, repost comment/reply
String created = data.getStringExtra("created");
if(created.equals("yes")) {
// Posting the comment again
getResultsFromApi();
}
}
}
Not the cleanest solution but it works.
The app that I am working on has two services (Pushwoosh and Helpshift) that use GCM for push notifications. I am attempting to implement the functionality shown here, in the Pushwoosh documentation, to allow both systems to function; https://docs.pushwoosh.com/v1.0/docs/gcm-integration-legacy. However my Android knowledge is failing me for how I actually route the bundle recieved to the relevant handlers. The project is dones in Unity but this is very much Android territory.
Here is the GcmListenerService class I have created that is very similar to the example;
import android.os.Bundle;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.gcm.*;
import android.content.ComponentName;
public class GCMListenerRouterService extends GcmListenerService
{
public GCMListenerRouterService()
{
super();
Log.i("Unity", "GCMListener - Constuctor");
}
private void dispatchMessage(String component, Bundle data)
{
Log.i("Unity", "GCMListener - dispatchMessage: " + (data != null ? data.toString() : "<null>") + " component: " + component);
Intent intent = new Intent();
intent.putExtras(data);
intent.setAction("com.google.android.c2dm.intent.RECEIVE");
intent.setComponent(new ComponentName(getPackageName(), component));
GcmReceiver.startWakefulService(getApplicationContext(), intent);
}
#Override
public void onMessageReceived(String from, Bundle data)
{
Log.i("Unity", "GCMListener - onMessageReceived: " + (data != null ? data.toString() : "<null>") + " from: " + from);
// Base GCM listener service removes this extra before calling onMessageReceived
// Need to set it again to pass intent to another service
//data.putString("from", from);
//if (TextUtils.equals(from, getString(R.string.PUSHWOOSH_PROJECT_ID)))
//{
// dispatchMessage(PushGcmIntentService.class.getName(), data);
//}
//else if (TextUtils.equals(from, getString(R.string.PRIVATE_PROJECT_ID)))
//{
// dispatchMessage(PrivateGCMListenerService.class.getName(), data);
//}
}
}
I am able to confirm that the push notification came from the correct messaging service, and I can determine if a push notifications came from one plugin or another. How do I route these bundle objects to the correct handler? I do not understand the following sample code;
if (TextUtils.equals(from, getString(R.string.PUSHWOOSH_PROJECT_ID)))
{
dispatchMessage(PushGcmIntentService.class.getName(), data);
}
else if (TextUtils.equals(from, getString(R.string.PRIVATE_PROJECT_ID)))
{
dispatchMessage(PrivateGCMListenerService.class.getName(), data);
}
I appreciate that this is example code but I can't find any functions in the Android documentation with the same signature as dispatchMessage. Do I need to make an intent service for each different type of message that is needed?
I know that for Helpshift I need to call a function with the signature handlePush(Context context, Bundle data) but I'm not sure what the Context object is. For Pushwoosh, i'm not sure what the handler is. While I am talking about two particular services I am assuming that this setup is a standard method for receiving messages and handling them.
Turns out that, for that for GCM, Bundle objects are the raw push details that you need to be handled. No further processing is needed and plugins/frameworks that support GCM should have a function that handles this, e.g. For helpshift that function is in com.helpshift.Core called handlePush(Context context, Bundle data).
Note that GCM is actually deprecated and Firebase Cloud Messenger is the new system going forward. This service has a different way of dealing with multiple push handlers and you should check your plugins for documentation on this.
I'm sorry for the long post, but I think this is the only way to explain whats happening... I'm looking for help after a lot of research with no answers... A big problem I think...
So this web app schedule a local notification based on a time picker that the users interact with..
I'm using cordova and jquery mobile multi-page system... It changes from page to page by div IDs, the navigation looks like this: index.html, index.html#page2, index.html#page3..
The local notification is a java plugin for cordova Katzer Local Plugin.
The plugin only works inside the onDeviceReady function and the jquery mobile does not, like this...
document.addEventListener('deviceready', onDeviceReady, false);
/* JQUERY MOBILE HERE */
$(document).on("pagecreate", "#home", function(event) {
$("a#btnpage2").click(function (e) {
e.stopImmediatePropagation();
e.preventDefault();
$( "#page2" ).pagecontainer( "change"); // change to #page2
});
});
$(document).on("pagecreate", "#page2", function(event) {
console.log("#page2 created");
});
function onDeviceReady(){
/* NOTIFICATION PLUGIN HERE */
//create notification
var msg = "notification message";
window.plugin.notification.local.add({
id: 'notif',
date: dateobject,
message: msg,
json: JSON.stringify({ test: msg }),
title: 'Title',
autoCancel: true,
ongoing: false
});
//onclick event notification
window.plugin.notification.local.onclick = function (notif, state, json) {
var msg = JSON.parse(json).test;
$( "#notificationPage" ).pagecontainer( "change", { text: msg} ); //pass data and change to #page2
}
//ontrigger notification
window.plugin.notification.local.ontrigger = function (notif, state, json) {
var msg = JSON.parse(json).test;
}
}
When the notification is fired, and when I click it, it should change the page to #notificationPage.
The problem is that the command inside onclick, does not work even when I click the notification with the app running, it throws this error:
Uncaugh Error: cannot call methods on pagecontainer prior to initialization; attempted to call method 'change'.
However the following command DOES change the page, found it on google: $.mobile.changePage( "#notificationPage" ). But only if the app is running and not interrupted. I think that if its in background or closed even if its not interrupted, it doesn't change the page... it opens the activity defined by the plugin. When I say in background or closed and not interrupted i mean that app was closed by the main button and not the back button that completely closes the app..
I guess this is the classes that handle the notification:
/* Receiver.class notification plugin */
Intent intent = new Intent(context, ReceiverActivity.class)
.putExtra(OPTIONS, options.getJSONObject().toString())
.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
int requestCode = new Random().nextInt();
PendingIntent contentIntent = PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return notification.setContentIntent(contentIntent);
/* ReceiverActivity.class notification plugin */
Context context = getApplicationContext();
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
launchIntent.putExtra("MSG", msgJson); // here I pass the json message to the new intent, thats gonna open when clicking the notification
context.startActivity(launchIntent);
So basically, I want to click the notification, and open a specific page, so I can get the json value on click and pass to that page and then display it to a div element... It seems I can't use the notification plugin commands outside the onDeviceReady, and neither jquery mobile commands inside the onDeviceReady.... Beside that I have to deal with the problem that is to do the same thing if the app is closed and interrupted...
At the java side, I could create another activity along with the main cordova app activity, and create a layout in xml, and add a textview... On the .java file of this new activity I think I could set the setContentView to this xml layout, and set the text of the textView to the json object value I want... this json value is the same as the message of the notification... I pretty sure, like 95% convinced this would work, not tested yet, but the thing is, its hard to maintenance.
What I tried is create this new activity, exactly like the main activity of cordova, but the loadUrl, I set to the page I want to go, not to LaunchUrl, which loads the address from config.xml of cordova, and passed the json value that I added as extra on the intent creation as a url param so on the jquery mobile side I could take the document.URL and the parameter... like this, first I edited the ReceiverActivity.class from notification plugin:
/* ReceiverActivity.class notification plugin */
Context context = getApplicationContext();
//String packageName = context.getPackageName();
Intent launchIntent = new Intent(context, NotificationActivity.class);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
launchIntent.putExtra("MSG", msgJson);
context.startActivity(launchIntent);
/* NotificationActivity.class cordova app second activity */
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
String msg;
Intent intent = getIntent();
Bundle extras = intent.getExtras();
msg = extras.getString("MSG");
String utf_encoded = null;
try {
utf_encoded = URLEncoder.encode(msg,"UTF-8");
} catch (UnsupportedEncodingException e) {}
String url = "file:///android_asset/www/index.html#notificationPage?parameter="+utf_encoded;
super.loadUrl(url);
}
And on the javascript side, I could retrive that parameter in the url:
document.addEventListener('deviceready', onDeviceReady, false);
$( document ).on( "pagebeforechange" , function ( event, data ) {
if ( data.toPage[0].id == "notificationPage" ) {
var url = document.URL;
var urlParams = decodeURIComponent(url);
var onlyParams = urlParams.split('=')[1];
var newchar = " ";
var paramValue = onlyParams.split('+').join(newchar);
$('#notificationPage #showMessage').empty();
$('#notificationPage #showMessage').append("<p>Message: " + paramValue + "</p>");
}
});
function onDeviceReady(){
/* ondeviceready */
}
This actually worked, but it has some bugs... Sometimes the page loads, sometimes the page doesn't load, the page sometimes turned to a black screen... It works specially if the app is closed and interrupted, but if its open, most of the times it goes to a black screen... and if I click the back button on the device, it "navigates back", but it actually goes to the page that should be activated and showing the message... its like the page is behind this black screen sometimes and it won't come out to front unless I use back button..
I'm out of options... tried almost everything with no concrete and stable solution.. Flags, javascript, java, redirect url at javascript, at java, nothing seems to work...
Well I'm not a developer. I'm a designer, putting all my efforts to finish this... but god, its hard.... Theoretically a easy solution would be leaving everything at defaults, and when the plugin "launch" the app or the intent, or whatever by clicking the notification, just run a javascript with the command from jquery mobile that change pages... That would be amazing! hahah
I really need help..
Thanks to everyone that is reading this... To everyone that will try to help me...
Thank you all
Use this method:
cordova.plugins.notification.local.on("click", function (notification) {
alert(notification.text);
}, scope);
Here is the updated doc.
Cordova Jquery Mobile Local Notification onclick change page