Parse-SDK-Flutter icon indicating copy to clipboard operation
Parse-SDK-Flutter copied to clipboard

ParseInstallation 'save' deletes the object from parse.

Open JTPenn-EG opened this issue 3 years ago • 13 comments

I am facing 2 issues here, 1 - When I call installation.create() and works fine (even updates current object if already created), but when I call installation.save() the installation gets deleted from the parse server, I really don't know why. (User is not logged in)

2 - installation.create() doesn't supply an installationId, not sure why but that what happens.

JTPenn-EG avatar May 04 '21 00:05 JTPenn-EG

You should not call create, because it is controlled internally by the SDK.

Try:

  var instalattion = await ParseInstallation.currentInstallation();
  print(instalattion.toString());
  if (instalattion.deviceToken == null) {
    instalattion.deviceToken = Uuid().v4().replaceAll('-', '');
  }
  instalattion.set<ParseUser>("user", currentUser);
  instalattion.subscribeToChannel("C");
  instalattion.subscribeToChannel("D");
  instalattion.subscribeToChannel("E");

  final apiResponse = await instalattion.save();

RodrigoSMarques avatar May 05 '21 01:05 RodrigoSMarques

I am experiencing some weird behaviour from ParseInstallation Let's say user A got his installation saved at parse, Go and delete A's installation manually from the dashboard Next time user A fetches currentInstallation and saves it, he will get a 101 error code indicating that the object doesn't exist. Now in order to be able to save his installation, if an 101 error occurs, I set the currentInstallation object id to null and save, a new installation gets created in the server. But the next time A opens and I call save on the current installation, I get a 101 code again and the previous installation automatically gets deleted 🤔

JTPenn-EG avatar May 18 '21 20:05 JTPenn-EG

Installation before save:
{"className":"_Installation","objectId":"DpZtDMt1jb","updatedAt":"2021-05-18T20:02:41.695Z","deviceType":"android"}

Performing save operation.
Error 101
Installation not found, creating new one

Resetting Current Installation objectId (to null)

Performing save operation.

Installation after save:
{"className":"_Installation","objectId":"HxYAh99bAR","createdAt":"2021-05-18T20:07:39.617Z","updatedAt":"2021-05-18T20:02:41.695Z","deviceType":"android"}

----------------------------------------------------------------------------------------- NEXT SESSION ("HxYAh99bAR" exists in dashboard in parse server)

Installation before save:
{"className":"_Installation","objectId":"HxYAh99bAR","createdAt":"2021-05-18T20:07:39.617Z","updatedAt":"2021-05-18T20:02:41.695Z","deviceType":"android"}

Performing save operation.
Error 101
Installation not found, creating new one

Resetting Current Installation objectId (to null)

Performing save operation.

Installation after save:
{"className":"_Installation","objectId":"UmEK42V6VJ","createdAt":"2021-05-18T20:07:44.263Z","updatedAt":"2021-05-18T20:02:41.695Z","deviceType":"android"}

Note: installation contains a device token and an installationId but I removed them the log above

Object "DpZtDMt1jb" did not exist in parse database, so another installation was created with "HxYAh99bAR" as id The next session "HxYAh99bAR" exists in parse, but still saving it would automatically delete it from database and returns a 101 error

Here is the code that handles saving the installation

  static Future<bool> _saveInstallation() async {
    print("Installation before save:");
    print(parseInstallation);
    print("Performing save operation.");
    var response = await parseInstallation.save();
    if (!response.success && response.error.code == 101) {
      print("Error code 101");
      print("Installation not found, creating new one");
      print("Performing save operation.");
      parseInstallation.objectId = null;
      response = await parseInstallation.save();
    }
    if (response.success){
      parseInstallation = await ParseInstallation.currentInstallation();
      print("Installation after save:");
      print(parseInstallation);
    }
    return response.success;
  }

EDIT: I've just noticed that although the locally stored installation has an InstallationId, the one saved at parse doesn't have an InstallationId (only when the bug happens) EDIT 2: Updating installation via dashboard or cloud code works fine

To illustrate exactly how to re-produce the bug: 1. First save an installation normally when user opens an app (each time user opens your app you call installation.save() to get some data like last session time and such) 2. For any reason you delete the installation manually from the dashboard. 3. Next time the user opens the app he will receive an error upon installtion.save() because the object doesn't exist anymore. 4. Manually remove the objectId field from the ParseInstallation.currentInstallation() object. and then save the installation. 5. A new object will be created succesfully. 6. Next time the user opens the app when he saves the installation, he will get a 101 code (although the object exists and it has a correct objectId locally).

JTPenn-EG avatar May 18 '21 20:05 JTPenn-EG

I believe the problem has something to do with installationId. After deleting the installation manually, next creations although they include an installationId locally, when they get saved in the server the installationId is null, I think the server refuses previously used installationIds. I temporarily fixed the issue by manually removing the current installation from local core storage if the save operation failed (to create a new installation with a new installation id).

JTPenn-EG avatar May 23 '21 20:05 JTPenn-EG

Closed. No activity in the last 14 days. If necessary open again.

RodrigoSMarques avatar Sep 14 '21 02:09 RodrigoSMarques

Hello all, I recently encountered this same bug while developing for iOS. I found the installation ID was not being set. Manually setting the installation ID in the database solved the bug. Adding in an additional call to set the installationId solved things for me.

if (call.method == 'set-installation-id') {
        var insty = await ParseInstallation.currentInstallation();
        insty.deviceToken = call.arguments.toString();
        insty.set("installationId", insty.installationId); // FIX
        // Only send the update to the server if the device token changed
        if (profile.installation == null) {
          await insty.save();
          profile.installation = insty;
          ProfileService(context).save(profile);
        }
      }

Hope this helps someone else I believe this points to a bug in _updateInstallation in parse_installation.dart

JesseAbeyta avatar Nov 03 '23 21:11 JesseAbeyta

Hello all, I recently encountered this same bug while developing for iOS. I found the installation ID was not being set. Manually setting the installation ID in the database solved the bug. Adding in an additional call to set the installationId solved things for me.

if (call.method == 'set-installation-id') {
        var insty = await ParseInstallation.currentInstallation();
        insty.deviceToken = call.arguments.toString();
        insty.set("installationId", insty.installationId); // FIX
        // Only send the update to the server if the device token changed
        if (profile.installation == null) {
          await insty.save();
          profile.installation = insty;
          ProfileService(context).save(profile);
        }
      }

Hope this helps someone else I believe this points to a bug in _updateInstallation in parse_installation.dart

Thank you for your information. I reopen issues to check it later.

mbfakourii avatar Nov 04 '23 06:11 mbfakourii

Thanks for opening this issue!

Hello , is there any solution for this yet ?

I am using

Flutter Parse SDK 5.1.3

Here is the ParseInstallation object {"className":"_Installation","objectId":"ID","createdAt":"2023-12-20T15:04:38.729Z","installationId":"ID","deviceType":"android","localeIdentifier":"en_US","timeZone":"Indian/Mayotte","appName":"name","appVersion":"1","appIdentifier":"name","parseVersion":"5.1.3","deviceToken":"TOKEN","pushType":"gcm"}

but installation doesnt get saved here is log

I/flutter (29881): ╭-- Parse Response I/flutter (29881): Class: _Installation I/flutter (29881): Function: ParseApiRQ.save I/flutter (29881): Status Code: 101 I/flutter (29881): Type: ObjectNotFound I/flutter (29881): Error: Object not found for update. I/flutter (29881): ╰--

sometime i get ":135,"stack":"Error: at least one ID field (deviceToken, installationId) must be specified in this operation

Fanom2813 avatar Dec 20 '23 16:12 Fanom2813

Hello , is there any solution for this yet ?

I am using

Flutter Parse SDK 5.1.3

Here is the ParseInstallation object {"className":"_Installation","objectId":"ID","createdAt":"2023-12-20T15:04:38.729Z","installationId":"ID","deviceType":"android","localeIdentifier":"en_US","timeZone":"Indian/Mayotte","appName":"name","appVersion":"1","appIdentifier":"name","parseVersion":"5.1.3","deviceToken":"TOKEN","pushType":"gcm"}

but installation doesnt get saved here is log

I/flutter (29881): ╭-- Parse Response I/flutter (29881): Class: _Installation I/flutter (29881): Function: ParseApiRQ.save I/flutter (29881): Status Code: 101 I/flutter (29881): Type: ObjectNotFound I/flutter (29881): Error: Object not found for update. I/flutter (29881): ╰--

sometime i get ":135,"stack":"Error: at least one ID field (deviceToken, installationId) must be specified in this operation

I also get this weird behaviour the current bypass is to clear the app cache and data in android and re open the app, it works after that. I cant find the root cause and no fix either .. going to do away with Parse installation and create my own class

funjay avatar May 28 '24 07:05 funjay

I'm also hitting this bug. Did anyone make any progress?

chadpav avatar Aug 05 '24 19:08 chadpav

I think I figured out the issue and a workaround. I could open a new issue to fully document because I think a number of people have reported this is various ways.

Issue:

  1. On Flutter client, create a ParseInstallation and call .save() (or use await ParsePush.instance.initialize() which does the same under the hood.)
  2. Somehow, the installation gets deleted in Parse Server. Not sure how all of my devices got into this state yet, but they are.
  3. When app opens again, call var parseInstallation = await ParseInstallation.currentInstallation();, update it and Save again. This is exactly what ParsePush.initialize() does.
  4. The Parse Server rejects this update with Error Code 101 ObjectNotFound (because it no longer exists)
  5. ParsePush eats this error with no output.

Results

  • With no Installation record, user never receives notifications again. The save() call silently fails on every app load.

First Workaround:

  1. I manually get the ParseInstllation.currentInstallation and try to save it but I handle the response.
  2. If not successful and code 101, I then call installation.create() to recreate a new installation on the server.

Results

  • The create() returns a 201 and updates local storage with the new installation object

That works but only for a second. I noticed on the Parse Server that the new Installation gets created with a new Object ID but the installationId field is NULL. Digging into the client code, it didn't think that field was dirty so it didn't save it to the server (e.g., _unsavedChanges collection didn't include that field). This wasn't a "real" create since you called create on a local object that already thinks it exists on the server.

Then, even with debug logging enabled on the server, my new installation object is missing with nothing in the log files. I haven't looked but I suspect there is some cleanup logic on the server that might delete invalid Installation records that don't have a unique installationId. This is just a hunch. I believe there is also cleanup code in the Parse Push Adapter to listen for FCM bad tokens and cleanup installation records so we don't continue to send messages to old tokens. That is exactly what FCM recommends you do as well.

The next time the flutter client reloads it tries to Save() the old object and returns Code 101 ObjectNotFound. You are in a loop all with silent errors.

Second work around:

  1. When you get an Code 101 ObjectNotFound after ParseInstallation Save() calls, call Create() but this time manually set the installation_id back to itself so that Parse.Object thinks it's dirty and includes it on the POST call.

Code snippet of what I'm doing on app start now:

    var parseInstallation = await ParseInstallation.currentInstallation();
    parseInstallation.set('deviceToken', token);

    final response = await parseInstallation.save();
    if (response.success) {
      _logger.debug('ParseInstallation saved', tag: tag);
    } else {
      await _logger.error('ParseInstallation save failed');

      // FIX - must set installation id or it wont get saved
      parseInstallation.set('installationId', parseInstallation.installationId);

      final createResponse = await parseInstallation.create();
      if (createResponse.success) {
        _logger.debug('ParseInstallation created', tag: tag);
      } else {
        await _logger.error('ParseInstallation create failed');
      }
    }

Results: Whenever your Installation gets cleaned up on the server, the client now detects that after the Save() fails, then it recreates a new installation with an installation ID and everything works again.

chadpav avatar Aug 06 '24 14:08 chadpav