PushNotificationDemo for Android fails to build

Yes, I’ve been through it in detail and I just cannot get it to work. Unfortunately the tutorial doesn’t cover troubleshooting of any kind, it just assumes everything works - really, as this involves config on another system it would be useful to have things like “if you never receive a token, check…”. Ideally vid tutorials would handle this topic much better (I also think the tutorial could be better organised and just have iOS in one go, Android in one go and MacOs in one go rather than jumping around between them). (The iOS stuff is fine, but pretty useless as in reality you need to be able to use it with Firebase too). I can’t get either iOS (some objective-C is needed to be written into your app and I don’t know where to do this as I’m not an objective-C person) and Android apps just never seem to receive the token (either my own, or the PushNotificationsDemo).

It would be really helpful if the creator of the tutorial could go through it and confirm that following the steps, they are able to successfully connect the PushNotificationsDemo program to Firebase and have it receive tokens etc.

Hi, I’ve looked into the code and a lot of the PushNotifications for Android does nothing unless:

JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME

is defined. I can’t see any mention of this #define and it’s not automatically done in my projects through Projucer that I can see. If I enable this flag then nothing builds anymore.

bump. any suggestions here?

bump

We’re looking at it.

3 Likes

great thx.

It got kicked here:

Actually need this too right now. So a quick fix would be awesome!

Hi guys,

Thank you both for raising this and for your patience. PushNotifications were indeed broken while reworking Android internals and they are now fixed in commit 7c46014

We have also added explanations to PushNotificationsDemo with all the steps that are required to make local and remote notifications work:

Let us know if there is anything that is unclear. When pulling from develop, remember to rebuild Projucer. To verify that everything works, you can fire up the PushNotificationsDemo, go to Remote tab and press “GetDeviceToken” button, you will get the dialog with your token, also displayed in the console in debug builds:

image

Then after launching the app go to Firebase Console and send a test message to your device using the token received:

image

image

4 Likes

Works like a charm, thank you!

1 Like

Just a quick question: When does the device token actually change? Because it says in the documentation of getDeviceToken:

[…] Always call this method to get the current token value.

I use the device token to register an endpoint in Amazon SNS. What does it mean, when the token changes?

Ok, so after a few test runs I now get this at startup:

tgkill 0x000000778ed3c410
abort 0x000000778eceebd4
art::Runtime::Abort(char const*) 0x000000778dc137e4
art::Runtime::Aborter(char const*) 0x000000778dc13ef4
android::base::LogMessage::~LogMessage() 0x000000778dcfea40
art::JavaVMExt::JniAbort(char const*, char const*) 0x000000778dab1c88
art::JavaVMExt::JniAbortF(char const*, char const*, ...) 0x000000778dab1f54
art::Thread::DecodeJObject(_jobject*) const 0x000000778dc3e8d4
art::ScopedCheck::CheckInstance(art::ScopedObjectAccess&, art::ScopedCheck::InstanceKind, _jobject*, bool) 0x000000778d8e3e20
art::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::JniValueType*) 0x000000778d8e2a7c
art::CheckJNI::GetStringCharsInternal(char const*, _JNIEnv*, _jstring*, unsigned char*, bool, bool) 0x000000778d8ebca0
_JNIEnv::GetStringUTFChars(_jstring*, unsigned char*) jni.h:847
juce::(anonymous namespace)::juceString(_JNIEnv*, _jstring*) juce_android_JNIHelpers.h:761
juce::(anonymous namespace)::juceString(_jstring*) juce_android_JNIHelpers.h:770
juce::JuceFirebaseInstanceIdService::tokenRefreshed(void*) juce_android_PushNotifications.cpp:1568
art_quick_generic_jni_trampoline 0x000000778dced304
art_quick_invoke_stub 0x000000778dce3f88
art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) 0x000000778d8b44f8
art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da5e15c
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58824
bool art::interpreter::DoInvoke<(art::InvokeType)1, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da84238
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da7d128
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da3fca0
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58804
bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da82abc
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da80558
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da3fca0
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58804
bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da82abc
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da80558
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da3fca0
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58804
bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da84c98
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da7dfe0
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da3fca0
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58804
bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da82abc
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da80558
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da3fca0
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58804
bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da84c98
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da7dfe0
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*) 0x000000778da3fca0
bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da58804
bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 0x000000778da84c98
art::JValue art::interpreter::ExecuteSwitchImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da7dfe0
art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool) 0x000000778da39604
artQuickToInterpreterBridge 0x000000778dcbf75c
art_quick_to_interpreter_bridge 0x000000778dced420
art_quick_invoke_stub 0x000000778dce3f88
art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) 0x000000778d8b44f8
art::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::ArgArray*, art::JValue*, char const*) 0x000000778dc0c820
art::InvokeVirtualOrInterfaceWithJValues(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, jvalue*) 0x000000778dc0d9ac
art::Thread::CreateCallback(void*) 0x000000778dc339f0
__pthread_start(void*) 0x000000778ed38d08
__start_thread 0x000000778ecf034c

This line seems to crash:

Edit:
Only happens after a freshly compiled install through Android Studio. If I start the app again directly after the crash, is works.

The token is refreshed in the following situations (see FirebaseInstanceIdService):

* App deletes Instance ID
* App is restored on a new device
* User uninstalls/reinstall the app
* User clears app data

it is odd that it only started crashing later, the crash would suggest that there is something wrong with the passed token. You get the crash after clean install because that’s when the token gets refreshed and the crashing line gets triggered. On successive start of the app you will not get the notification. Do you test it on a real device? What Android version is it? Please try the following fix and let me know if it works for you (uninstall and reinstall the app, and make sure that this line is definitely called after changing the code):

static void JNICALL tokenRefreshed (JNIEnv*, jobject, void* token)
{
    if (auto* instance = PushNotifications::getInstanceWithoutCreating())
        instance->pimpl->notifyListenersTokenRefreshed (juceString (static_cast<jstring> (token)));
}

So I came back into the office and I can’t reproduce the problem anymore. I might have used the old version of projucer by accident. If the problem occurs again I will try your change. Thanks for the help!

Hi, looking good now - thanks alot!

I’m trying to get Firebase up and running on iOS also but need to perform this step of adding this to my project, but not sure how/where to do this as not an obj-C programmer. Any ideas? thx

image

You need to use START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE macro stating your custom UIApplicationDelegate implementation, then you need to implement that delegate in ObjC

1 Like

Hi. thx. is there an example of doing this anywhere?

Here’s a barebone implementation which calls the function you need and it also instantiates JUCE’s delegate internally. Note that we need to be forwarding messages to JUCE delegate so that JUCE can do its things as usual.

@implementation CustomAppDelegate

-(id) init
{
    self = [super init];

    juceDelegate = reinterpret_cast<NSObject<UIApplicationDelegate>*> ([[NSClassFromString (@"JuceAppStartupDelegate") alloc] init]);

    return self;
}

-(void)dealloc
{
    [juceDelegate release];
    [super dealloc];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [FIRApp configure];

    if (juceDelegate != nullptr)
    {
        SEL selectorWithOptions = NSSelectorFromString (@"application:didFinishLaunchingWithOptions:");
        SEL selectorNoOptions   = NSSelectorFromString (@"applicationDidFinishLaunching:");

        if ([juceDelegate respondsToSelector: selectorWithOptions])
            return [juceDelegate application:application
               didFinishLaunchingWithOptions:launchOptions];

        if ([juceDelegate respondsToSelector: selectorNoOptions])
            [juceDelegate applicationDidFinishLaunching:application];
    }

    return YES;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if (juceDelegate != nullptr && [juceDelegate respondsToSelector:[anInvocation selector]])
        [anInvocation invokeWithTarget:juceDelegate];
    else
        [super forwardInvocation:anInvocation];
}

-(BOOL)respondsToSelector:(SEL)aSelector
{
    if (juceDelegate != nullptr && [juceDelegate respondsToSelector:aSelector])
        return YES;

    return [super respondsToSelector:aSelector];
}

- (id)performSelector:(SEL)aSelector withObject:(id)object
{
    bool responds = [self respondsToSelector: aSelector];
    jassert (responds);

    if (responds)
    {
        if (juceDelegate != nullptr && [juceDelegate respondsToSelector: aSelector])
            return [juceDelegate performSelector: aSelector withObject: object];

        return [super performSelector: aSelector withObject: object];
    }

    return nil;
}

@end

For additional details on other iOS functions, have a look at Apple docs and Obj-C language.

1 Like

I have another PushNotification question, sorry to hijack the thread.
In order to display push notification data correctly in my app, I need custom data.
But there are three different ways of receiving notifications:

  1. The app is open.
    The notification contains standard field like title and body. Custom data can be found under properties[“data”][“customKey”].

  2. The app is closed and I click on the Android notification.
    The app will open and a new notification is received. But this time, the data can be found directly under properties[“customKey”].

  3. The app is closed, I receive a notification and open the app directly.
    I get all delivered and unread notifications via PushNotifications::getDeliveredNotifications(). In the async callback I get an array of Notifications. But this time the layout is changed again. It seems these Notifications don’t contain the custom data at all.

My problem is with the last point. Is it not possible to get the custom data at all here?

@lukasz.k any ideas?