0

I'm trying to retrieve user data from Parse (xamarin.ios using c#). I'm using an async method with await. My challenge is,each time I navigate to the tableView in the app, which should populate the user data in question,the table is always empty.

I would like to wait until the results have been returned before proceeding with the other portion of code.I have tried to use the ContinueWith() function but constantly ran into a build error -

Cannot implicitly convert type 'void' to System.Collections.Generic.IEnumerable<Parse.ParseObject>

My Questions:

  1. Is this the best way to wait for the result?
  2. How do I solve the build error?

Here is my current implementation:

public async void retrieveData(string username)
    {
        try
        {

            this.requests.ClearRequests();
            refreshed = false;
            var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);

            IEnumerable<ParseObject> results = await query.FindAsync().ContinueWith(t =>{

                if(results != null)
                {
                    foreach(ParseObject parseObject in results)
                    {
                        UserRequest request = new UserRequest();
                        request.objectId = parseObject.ObjectId;
                        request.make = parseObject.Get<string> ("item1");
                        request.model = parseObject.Get<string> ("item2");
                        request.year = parseObject.Get<string> ("item3");
                        request.userName = parseObject.Get<string> ("username");
                        this.requests.addRequest (request);
                    }
                    refreshed = true;
                }



            });
}
        catch(ParseException e) {
            Console.WriteLine (e.Message + e.StackTrace);
        }
    }
naffie
  • 679
  • 1
  • 12
  • 29

2 Answers2

1

You shouldn't need a ContinueWith...that's what the await should handle.

await waits on a Task and then brings back the result with the proper return type. ContinueWith returns a Task, so you would have to grab the Result from the task to make it usable.

For more on this type of thing, you may want to check out Difference between await and ContinueWith

You can try something like this.

public async void retrieveData(string username, )
    {
        try
        {

            this.requests.ClearRequests();
            refreshed = false;
            var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);

            IEnumerable<ParseObject> results = await query.FindAsync();

            if(results != null)
            {
                foreach(ParseObject parseObject in results)
                {
                    UserRequest request = new UserRequest();
                    request.objectId = parseObject.ObjectId;
                    request.make = parseObject.Get<string> ("item1");
                    request.model = parseObject.Get<string> ("item2");
                    request.year = parseObject.Get<string> ("item3");
                    request.userName = parseObject.Get<string> ("username");
                    this.requests.addRequest (request);
                }
                refreshed = true;
            }
            //This is your refresh method for your TableView
            this.RefreshTableView();
            //or, if in iOS
            NSNotificationCenter.DefaultCenter.PostNotificationName("resultsRetrieved", null);
        }
        catch(ParseException e) {
            Console.WriteLine (e.Message + e.StackTrace);
        }
    }

To show the results in the tableView, I would recommend moving the refreshing of the tableView to a separate method that gets triggered synchronously after the results have been retrieved and parsed. This is shown with the this.RefreshTableView() call above.

If in iOS on Xamarin, another option is to post a notification to the NSNotificationCenter (the Xamarin documentation for which is here). Use the PostNotificationName part seen above instead and then add an observer in the ViewControllers that you want to be dependent on the data. This is done as follows:

Make a notificationToken object:

    NSObject notificationToken;

Then in your setup method (you could put this inside of your ViewDidLoad):

void Setup ()
{
    notificationToken = NSNotificationCenter.DefaultCenter.AddObserver ("resultsRetrieved", RefreshData);
}

Make your RefeshData method:

void RefreshData (NSString notifString)
{
    this.tableView.ReloadData();
}

And then, make sure you dispose of the notification observer when you tear down the class

void Teardown ()
{
    NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
}
Community
  • 1
  • 1
Leejay Schmidt
  • 1,193
  • 1
  • 15
  • 24
  • Hi Leejay, Thanks fro your quick response. I understand that. So how do I check if the the task has been completed before bringing the screen into view? – naffie Jan 06 '16 at 16:01
  • Okay, now I have a new question based on that response.Picture this scenario: 1. I call the retrieveData() function in the AppDelegate,as soon as the user logs in. 2. The tableview isn't the initial screen,so await has time to return results. 3.Navigating to the tableview displays the items. 3. Selecting an item from the list opens a new screen where you can edit it/create a new one. 4. Clicking the save button saves the data and pops this view from the stack, bringing us back to the tableview. The tableview doesn't reflect the changes though. Should I call the retrieveData() function again? – naffie Jan 06 '16 at 16:37
  • If yes, where should I call it? And in my tableViewController, should I be working inside the ViewWillAppear or the ViewDidLoad? I'm currently using the ViewWillAppear because the ViewDidLoad is called just once. I believe the view is displayed long before the await returns a result. I already tried calling the retrieveData() inside the save button delegate. – naffie Jan 06 '16 at 16:41
  • Ah, you're doing iOS in Xamarin. The easiest way to do this might be to use `NotificationCenter` then, and `post` a notification once the retrieval is complete. Then, in the `selector` for the `notification`, you can do `reloadData` on your `tableView`. [Here's more data on how to do that from Xamarin](https://developer.xamarin.com/api/type/MonoTouch.Foundation.NSNotificationCenter/). – Leejay Schmidt Jan 06 '16 at 17:09
  • I just edited the answer to have more information about how to do this. – Leejay Schmidt Jan 06 '16 at 17:24
  • This should happen in the background without the user's knowledge. I wouldn't want the reloading to be dependent on user action. I there another way around it? Perhaps another alternative? – naffie Jan 06 '16 at 17:25
  • Why would reloading be dependent on user action? The `AppDelegate` would post the notification and the view controller with the notification observer would automatically reload the `tableView` data. The user wouldn't be required to do anything, and it would all be handled in the background. – Leejay Schmidt Jan 06 '16 at 17:34
  • Oh,sorry.My bad. I guess I didn't really understand how that works. Let me look at the link, give it a try and let you know how it goes. Thanks a lot! – naffie Jan 06 '16 at 17:55
  • No problem! Good luck! – Leejay Schmidt Jan 06 '16 at 18:08
0

I had a similar issue so started using callbacks. I'm using them in Xamarin.Android, pretty sure they're available in Xamarin.iOS.

Method that starts the task method - Note I am passing in a method of this class as a parameter

private async void updatedData()
    {
        await Utils.DataTasks.getNewLiveTips(populateTipsList);
    } 

Method that calls for data from server

public class DataTasks
{
    public static async Task getAllData(Action<IEnumerable<ParseObjects>> callback) { 

        var query = new ParseQuery<ParseObjects>().OrderByDescending("updatedAt").Limit(5);
        IEnumerable<ParseObjects> parseTips = await query.FindAsync();

        foreach (var tip in parseTips)
        {
         // Save data to DB if needed 
        }

        callback(parseTips);
    }

Method I passed as parameter in the first instance is now called

private void populateTipsList(IEnumerable<ParseObjects> results)
    {
        mAdapter = new TipAdapter(this.Activity, results);
        mRecyclerView.SetAdapter(mAdapter);

        refresher.Refreshing = false;
    }
Phill Wiggins
  • 2,577
  • 4
  • 28
  • 33