none
[Azure Mobile table] Using Version, UpdatedAt fields leads to unnecessary pull operation RRS feed

  • Question

  • Hello,

    if I use the "Version" and "UpdatedAt" field I had to pull my changes after the push, because otherwise these fields, which are changed by the server, are not updated automatically after the insert/update operation. I would assume, that the new values are returned from the server automatically and that I don't need another round trip to pull the data again which I just pushed to the server.

    Or have I missed anything here?

    Thanks

    Friday, July 1, 2016 9:08 AM

Answers

  • Thanks Donna,

    I created an issue (https://github.com/Azure/azure-mobile-apps-android-client/issues/67).

    Best Regards!

    • Marked as answer by MonRoyals Thursday, July 7, 2016 8:14 AM
    Thursday, July 7, 2016 8:14 AM

All replies

  • It depends on how your client is handling the additional fields. There are two methods - you can calculate them on the client or you can calculate them on the server. If you leave it to the server, then yes - you need to pull them.

    Perhaps you can post some of your code so we can advise further?

    Friday, July 1, 2016 4:50 PM
    Moderator
  • Hello,

    here is some part of my code. If you need the whole source I can give you the rest of the code (it's not much). The updateData method updates or inserts items and then syncs the data afterwards. The initLocalStore method initalizes the store. 

    How can I calculate the fields by my self, especially the version field?

    public AsyncTask<Void, Void, Void> init(final Activity activity, final VAServicesCallback callback, final String product) {
            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
                    boolean success = true;
                    mCallback = callback;
                    mProduct = product;
                    mActivity = activity;
                    try {
                        createClient().get();
                        loadUserTokenCache(mClient, AuthenticationProvider.NONE, null);
                    } catch (Exception e) {
                        log(e, "Error");
                        success = false;
                    }
                    methodFinished("init", success, null);
                    return null;
                }
            };
            return runAsyncTask(task);
        }
    
        private void methodFinished(String method, boolean success, JsonObject jsonResult) {
            JsonObject result = new JsonObject();
            result.addProperty("method", method);
            result.addProperty("success", success);
            result.add("data", jsonResult);
            mCallback.methodFinished(result.toString());
        }
    
        private AsyncTask<Void, Void, Void> createClient() {
            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
                    try {
                        mClient = new MobileServiceClient("https://example.net", mActivity);
                        mGameItemPlayerTable = mClient.getSyncTable("GameItemPlayer", GameItemPlayer.class);
                        // Extend timeout from default of 10s to 25s
                        mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
                            @Override
                            public OkHttpClient createOkHttpClient() {
                                OkHttpClient client = new OkHttpClient();
                                client.setReadTimeout(25, TimeUnit.SECONDS);
                                client.setWriteTimeout(25, TimeUnit.SECONDS);
                                return client;
                            }
                        });
    
                        initLocalStore().get();
                    } catch (MalformedURLException e) {
                        log(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
                        throw new RuntimeException(e);
                    } catch (Exception e) {
                        log(e, "Error");
                        throw new RuntimeException(e);
                    }
                    return null;
                }
            };
    
            return runAsyncTask(task);
        }
    
        private AsyncTask<Void, Void, Void> initLocalStore() {
            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
                    try {
                        MobileServiceSyncContext syncContext = mClient.getSyncContext();
    
                        if (syncContext.isInitialized())
                            return null;
    
                        mLocalStore = new SQLiteLocalStore(mClient.getContext(), "OfflineStoreVertexArts129", null, 1);
    
                        Map<String, ColumnDataType> tableDefinitionGameItemValues = new HashMap<String, ColumnDataType>();
                        tableDefinitionGameItemValues.put("Id", ColumnDataType.String);
                        tableDefinitionGameItemValues.put("LongValue", ColumnDataType.Integer);
                        tableDefinitionGameItemValues.put("StringValue", ColumnDataType.String);
                        tableDefinitionGameItemValues.put("DecimalValue", ColumnDataType.Real);
                        tableDefinitionGameItemValues.put("DateValue", ColumnDataType.Date);
                        tableDefinitionGameItemValues.put("Item_ID", ColumnDataType.Integer);
                        tableDefinitionGameItemValues.put("Player_ID", ColumnDataType.Integer);
                        tableDefinitionGameItemValues.put("Version", ColumnDataType.String);
                        tableDefinitionGameItemValues.put("UpdatedAt", ColumnDataType.DateTimeOffset);
                        mLocalStore.defineTable("GameItemPlayer", tableDefinitionGameItemValues);
    
                        syncContext.initialize(mLocalStore, new VASyncHandler()).get();
                    } catch (final Exception e) {
                        log(e, "Error");
                        throw new RuntimeException(e);
                    }
    
                    return null;
                }
            };
    
            return runAsyncTask(task);
        }
    
        public AsyncTask<Void, Void, Void> updateData(final JsonObject jsonGameData) {
            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
    
                    Map<Integer, GameItemPlayer> mapGameItems = new HashMap<Integer, GameItemPlayer>();
                    JsonObject root = new JsonObject();
                    boolean success = true;
                    try {
                        List<GameItemPlayer> items = mGameItemPlayerTable.read(null).get();
                        for (GameItemPlayer item : items) {
                            mapGameItems.put(item.mItemID, item);
                        }
    
                        boolean bHasChanges = false;
                        JsonArray jsonArray = jsonGameData.getAsJsonArray("GameItems");
                        for (int i = 0; i < jsonArray.size(); i++) {
                            JsonObject gameItem = (JsonObject) jsonArray.get(i);
                            int gameItemID = gameItem.get("ID").getAsInt();
                            GameItemPlayer item = mapGameItems.get(gameItemID);
                            boolean bAdd = false;
                            if (item == null) {
                                bAdd = true;
                                item = new GameItemPlayer();
                            }
    
                            item.mItemID = gameItemID;
                            item.Player_Id = getUserId();
    
                            DataTypeGameItem type = DataTypeGameItem.values()[gameItem.get("Type").getAsInt()];
                            double decimalValue = gameItem.get("DecimalValue").getAsDouble();
                            String stringValue = gameItem.get("StringValue").getAsString();
                            long longValue = gameItem.get("LongValue").getAsLong();
                            Date dateValue = new Date(gameItem.get("LongValue").getAsLong() * 1000);
                            boolean bChanged = false;
    
                            if (type == DataTypeGameItem.Decimal && (item.mDecimalValue == null || item.mDecimalValue != decimalValue)) {
                                bChanged = true;
                                item.mDecimalValue = decimalValue;
                            } else if (type == DataTypeGameItem.String && (item.mStringValue == null || !item.mStringValue.equals(stringValue))) {
                                bChanged = true;
                                item.mStringValue = stringValue;
                            } else if (type == DataTypeGameItem.Long && (item.mLongValue == null || item.mLongValue != longValue)) {
                                bChanged = true;
                                item.mLongValue = longValue;
                            } else if (type == DataTypeGameItem.Date && (item.mDateValue == null || !item.mDateValue.equals(dateValue))) {
                                bChanged = true;
                                item.mDateValue = dateValue;
                            }
    
                            bHasChanges |= bChanged;
                            if (bChanged && bAdd) {
                                mGameItemPlayerTable.insert(item).get();
                            } else if (bChanged) {
                                mGameItemPlayerTable.update(item).get();
                            }
                        }
    
                        if (isLoggedIn() && bHasChanges) {
                            sync().get();
                        }
    
                        getData(root, false);
                    } catch (Exception e) {
                        log(e, "updateData");
                        success = true;
                    }
    
                    methodFinished("updateData", success, root);
                    check();
    
                    return null;
                }
            };
    
            return runAsyncTask(task);
        }


    • Edited by MonRoyals Friday, July 1, 2016 5:17 PM typo
    Friday, July 1, 2016 5:16 PM
  • Those fields should automatically be updated for you after you push, since the server should return the new value as part of the insert/update operation. I recommend you add a logging handler to your client to make sure that these are being returned in the response: https://github.com/Azure/azure-mobile-apps/wiki/Help,-my-app-isn't-working!#android-client

    However, if you have any objects in memory, they won't get these new values, and you need to read from your local store again. You shouldn't need to do another pull operation.

    • Proposed as answer by Swikruti Bose Saturday, July 2, 2016 3:00 PM
    Friday, July 1, 2016 10:50 PM
    Moderator
  • Hi Donna, 

    thanks for your suggestions. I debugged the code and found the issue. The problem was that my fields are starting with a uppercase letter, but the Json response starts with a lower case letter. Thats really strange, because on the server and on the client everythings starts with a uppercase letter.

    The method MobileServiceTable::patchOriginalEntityWithResponseEntity() is case sensitive. So for me it looks like a bug, because you transform the response from a upper case letter to a lower case letter and so the patch method does not work. The resulting sql statement contains all fields twice. At least an exception must be thrown, because it's really hard to find such an issue.

    Thanks for the great support

    Wednesday, July 6, 2016 7:40 AM
  • Thanks for the info. Could you please file a bug report in the Android client repo so we can fix?  https://github.com/azure/azure-mobile-apps-android-client

    Thanks!

      
    Wednesday, July 6, 2016 9:29 PM
    Moderator
  • Thanks Donna,

    I created an issue (https://github.com/Azure/azure-mobile-apps-android-client/issues/67).

    Best Regards!

    • Marked as answer by MonRoyals Thursday, July 7, 2016 8:14 AM
    Thursday, July 7, 2016 8:14 AM