NappyXIII NappyXIII - 2 months ago 12
Android Question

Best way to wait for an asychronous network task within AsyncTask

I'm working through the Vimeo API in my android app to pull the link for a particular vimeo video from my Vimeo Pro account and then download this video through an AsyncTask. The read/write of the file is fine, but I'm having trouble pulling the link and passing it to that method.

The code is as follows:

class DownloadFileFromURL extends AsyncTask<String, String, String> {
...
@Override
protected String doInBackground(final String... args) {
// args[1]/name is the output file name
String name = args[1];
...
// pos is the position within the vimeo channel's array
final int pos = Integer.parseInt(args[3]);

//Here is the main code, args[2] is the channel id for
//the specific vimeo channel that the code
//needs to pull the video files from.
VimeoClient.getInstance().fetchNetworkContent(args[2], new ModelCallback<VideoList>(VideoList.class) {
@Override
public void success(VideoList videoList) {

//If the video exists, get the video object,
//and then get the video files related to that object from download[].
if (videoList != null && videoList.data != null && !videoList.data.isEmpty()) {
Video video = videoList.data.get(pos);
ArrayList<VideoFile> videoFiles = video.download;

// Get the video file, and then get it's link, store as string.
if(videoFiles != null && !videoFiles.isEmpty()) {
VideoFile videoFile = videoFiles.get(2); // you could sort these files by size, fps, width/height
String link = videoFile.getLink();

**link = [test direct link to mp4];
DownloadFile(link, args[1]);**
}
}
}
...
});

**//String link = [test direct link to mp4];
//DownloadFile(link, args[1]);**
}
...
}


The string variable and DownloadFile(String link, string outputName) lines towards the end of the code is my main problem. I printed out the link's from videoFile.getLink() and used that as a test link for the code. When I run string link = xxx, and DownloadFile outside of the vimeoClient.fetchNetworkContent (as commented out at the very end), the code works while when they are placed inside the fetchNetworkContent() method, it hits a NetworkOnMainThreadException.

The problem is that I need to retrieve the link before DownloadFile() runs. Is there a way for me to fix this within the fetchNetworkContent? Or is there a way for me to force the system to wait before the DownloadFile() that is commented out until the networkFetchContent is complete?

EDIT: So I updated my code based on cricket_007's answer of chaining AsyncTasks. Instead of creating a second AsyncTask though, I decided to just loop it through the same task with a logic system.

First running DownloadFileFromURL() basically asks, what information am I given?

If given a url, it will run DownloadFile(url, outputtedFileName).
If not, if it receives the keyword "vimeo", it uses the vimeoClient to find the link, which then runs DownloadFileFromURL(vimeoLinkURL, outputtedFileName) from within. I just used a logic tree, I suppose.

class DownloadFileFromURL extends AsyncTask<String, String, String> {

@Override
protected void onPreExecute() {
super.onPreExecute();
}

/**
* Downloading file in background thread
*/
@Override
protected String doInBackground(final String... args) {
final String name = args[1];

// Check if this is already a url link ending in .mp4
if(FilenameUtils.isExtension(args[0], "mp4")){
DownloadFile(args[0], args[1]);
}

//If not, is it a vimeo video? Check with keyword "vimeo"
else if (args[0].contains("vimeo")){
final int pos = Integer.parseInt(args[3]);
VimeoClient.getInstance().fetchNetworkContent(args[2], new ModelCallback<VideoList>(VideoList.class) {
@Override
public void success(VideoList videoList) {
Log.d("VimeoClient", "Success in VimeoList Reading");

if (videoList != null && videoList.data != null && !videoList.data.isEmpty()) {

Video video = videoList.data.get(pos);
ArrayList<VideoFile> videoFiles = video.download;

if(videoFiles != null && !videoFiles.isEmpty()) {
VideoFile videoFile = videoFiles.get(2); // you could sort these files by size, fps, width/height

String link = videoFile.getLink();
new DownloadFileFromURL().execute(link, args[1], args[2], args[3]);
}
}
}

@Override
public void failure(VimeoError error) {
Log.d("VimeoError", "Failure in VideoList Reading in VideoDownloader class");
}
});
// return null so that the Toast is not created for completion
// since this ends in DownloadFile()
return null;
}
return name;
}

@Override
protected void onPostExecute(String fileName) {
if(fileName != null) {
Toast.makeText(mContext, "Completed download of " + fileName, Toast.LENGTH_LONG).show();
}
}
}


I've marked his answer as correct, not because it was the final code, but I find it much more informative than the specific code that I used. My code really is specific to my particular solution in this use case, but his explanation was the real solution.

Answer

It isn't clear what the DownloadFile method is doing, but you might need a secondary AsyncTask for it.

Sidenote: I would hope that VimeoClient.getInstance().fetchNetworkContent would run in it's own thread anyway, so it shouldn't need an AsyncTask, but we'll pretend it doesn't.

Recommended approach to "wait" for an AsyncTask is to carefully chain the calls together. For example, if you give one AsyncTask the callback, then the success method gets ran. From within that success, you can start a new task that will call DownloadFile (probably give it the whole ArrayList<VideoFile> rather than just one link if you want to download all the links)

class DownloadFileFromURL extends AsyncTask<String, Void, Void> {
    private ModelCallback<VideoList> callback;

    public DownloadFileFromURL(ModelCallback<VideoList> callback) {
        this.callback = callback;
    }

    @Override
    protected Void doInBackground(final String... args) {

        //Here is the main code, args[0] is the channel id for 
        //the specific vimeo channel that the code 
        //needs to pull the video files from.
        VimeoClient.getInstance().fetchNetworkContent(args[0], callback);

    }
    ...
}

Then, wherever you call that task, pass in the interface for the action you want to perform

// Need these values inside the callback - have to be final
final String arg1;
final int pos; 

// The callback that is hit from doInBackground()
ModelCallback<VideoList> callback = new ModelCallback<VideoList>(VideoList.class) {
    @Override
    public void success(VideoList videoList) {

       //If the video exists, get the video object, 
       //and then get the video files related to that object from download[].
        if (videoList != null && videoList.data != null && !videoList.data.isEmpty()) {
            Video video = videoList.data.get(pos);
            ArrayList<VideoFile> videoFiles = video.download;

            // Get the video file, and then get it's link, store as string.
            if(videoFiles != null && !videoFiles.isEmpty()) {
                VideoFile videoFile = videoFiles.get(2); // you could sort these files by size, fps, width/height
                String link = videoFile.getLink();

                // TODO: Might need to execute another AsyncTask
                DownloadFile(link, arg1);
            }
        }
    }
    ...
};

// pass the callback to the task 
new DownloadFileFromURL(callback).execute("channel_id");
Comments