Load all resources before loading Activity? - android

I have an activity which has to grab heavy images, resize them and loads into GridView. Since all this process of fetching, resizing is time consuming, when I try to load this screen from another ACtivity, I get a delay.
How can I first load the content of this "grid activity" and open it only after all images have been loaded?
Using startActivityForResult? If this is a solution, how can I detect that all images have been loaded since GridView load images on scroll.
EDIT
I was suggested to use AsyncTask which I used dozens of times before. But something confuses me.
Should I send custom adapter initialization to the AsyncTask?
mGridView = (GridView) findViewById(R.id.gridview);
mImageAdapter = new ImageAdapter(this);
mGridView.setAdapter(mImageAdapter);
Or I should do async task inside of the custom adapter, namely inside the method getView and with each gridview cell create a new call to my AsyncTask?

You can use the AsyncTask by putting all the code of fetching and resizing in the doInBacground method, you can save data in an object (may be an ArrayList of Bitmap) to be returned to the onPostExecute method (see AsyncTask genric types).
Then will also have to change your ImageAdapter constructor by adding the object you got from AsyncTask to it as a parameter.
public class DownloadPdfTask extends AsyncTask<Void, Integer, ArrayList<Bitmap>> {
private ArrayList<Bitmap> images;
#Override
protected ArrayList<Bitmap> doInBackground(Void... arg0) {
// TODO Auto-generated method stub
/** your long task here, and set "images" **/
return images;
}
#Override
protected void onPostExecute(ArrayList<Bitmap> images) {
// TODO Auto-generated method stub
super.onPostExecute(images);
mGridView = (GridView) findViewById(R.id.gridview);
mImageAdapter = new ImageAdapter(this, images); // new construcor
mGridView.setAdapter(mImageAdapter);
}
}
Use the images list as you want in your Adapter !

Try creating an AsyncTask.
Create your Activity normally. At the end of onCreate(), start your AsyncTask.
On your AsyncTask's onPreExecute() method, make some animation or message appear to the user.
On doInBackground() work on your images.
On onPostExecute(), load your images on the GridView and stop the animation or dismiss the message.
This way, your Activity won't have the delay you mentioned while being created.

Related

Using async Task to load an apps listView with images in Android

I have a fragment dialog containing a list of all the apps installed on the device.
Sometimes the loading takes some time and I would like to show a ProgressDialog while it loads.
I've tried the following code which didn't do any good :
#Override
public void onAttach(Activity activity) {
// Show progress dialog
showProgressDialog(activity);
super.onAttach(activity);
}
private void showProgressDialog(Activity activity) {
mProgressDialog = new ProgressDialog(activity);
mProgressDialog.setTitle(R.string.loading);
mProgressDialog.setMessage(getString(R.string.shared_options_wait_while_applist_loads));
mProgressDialog.show();
}
The onCreate loads the whole list and the app images, and then I use :
#Override
public void onStart() {
stopProgressDialog();
super.onStart();
}
private void stopProgressDialog() {
mProgressDialog.dismiss();
}
Now I'm thinking about loading the whole list in a async task, but I can't figure what should the async task do, it should probably load the list, but how can I wait for the list and get the list when it's ready ? (I believe something like a callback should be used?)
Thanks
You can watch dataset changes in your adapter by adding a DataSetObserver.
#Override
public void onCreate(Bundle savedInstanceSate){
super.onCreate(savedInstanceState);
setContentview(R.layout.main_view);
ListView myList = (ListView) findViewById(R.id.my_list);
MyDataTypeAdapter adapter = new MyDataTypeAdapter();
myList.setAdapter(adapter);
adapter.registerDataSetObserver( new DataSetObserver(){
#Override
public void onChange(){
//do stuff here
}
});
}
This way onChange will be loaded when your dataSet changes and you can download your image there.
However, I will better do an AsyncTask for each row in your adapter and download it's image independently. You could also use UniversalImageLoader library for this purpose.
you can try Android Bitmap Fun example
Android Image Demo : http://developer.android.com/training/displaying-bitmaps/index.html
This example with GridView, you can use the same adapter for ListView by changing view in listItem.

AsyncTask runs on each page of the ViewPager

I have 3 Tabs like in the android development tutorial
Now what I want to do is very simple I use Fragments on each page. I want to show different content from a rss feed on each page.
The problem is when I go to the next tab it runs AsyncTask (which is in onCreateView) of the previous Fragment.
So you start on Page 1 it loads the content fine. Then when you go to Page 2 is runs the onCreateView of the Fragment of Page 1 again. And obviously gives an NullException. The point is it should not be running AsyncTask of Page 1 at all at that Page 2.
I don't think there is any example code needed if so tell me which part you need to see. Then I will edit my question.
AsyncTask inside a ListFragment :
public class MyAsyncTask extends AsyncTask<List<String>, Void, List<String>>
{
// List of messages of the rss feed
private List<Message> messages;
private volatile boolean running = true;
#SuppressWarnings("unused")
private WeakReference<NieuwsSectionFragment> fragmentWeakRef;
private MyAsyncTask(NieuwsSectionFragment fragment)
{
this.fragmentWeakRef = new WeakReference<NieuwsSectionFragment>(fragment);
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
mProgress.show();
// progress.setVisibility(View.VISIBLE); //<< set here
}
#Override
protected void onCancelled()
{
Log.w("onCancelled", "now cancelled");
running = false;
}
#Override
protected List<String> doInBackground(List<String>... urls)
{
FeedParser parser = FeedParserFactory.getParser();
messages = parser.parse();
List<String> titles = new ArrayList<String>(messages.size());
for (Message msg : messages)
{
titles.add(msg.getTitle());
// Log.w("doInBackground", msg.getTitle());
}
return titles;
}
#Override
protected void onPostExecute(List<String> result)
{
super.onPostExecute(result);
mProgress.dismiss();
if (result != null)
{
PostData data = null;
listData = new PostData[result.size()];
for (int i = 0; i < result.size(); i++)
{
data = new PostData();
data.postTitle = result.get(i);
data.postThumbUrl = "http://igo.nl/foto/app_thumb/28991-Taxi-vast-na-poging-tot-nemen-van-sluiproute.jpg";
listData[i] = data;
Log.w("onPostExecute", "" + listData[i].postTitle);
}
adapter = new PostItemAdapter (getActivity(), android.R.layout.simple_list_item_1, listData);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
}
It's called inside a method and that method is executed inside the onCreateView of the ListFragment :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
startNewAsyncTask();
View rootView = inflater.inflate(R.layout.fragment_section_nieuws, container, false);
return rootView;
}
#SuppressWarnings("unchecked")
public void startNewAsyncTask()
{
MyAsyncTask asyncTask = new MyAsyncTask(this);
this.asyncTaskWeakRef = new WeakReference<MyAsyncTask>(asyncTask);
asyncTask.execute();
}
The LogCat :
Try using isAdded() before onPostExecute(). isAdded() returns true if the fragment is currently added to its activity.
http://developer.android.com/reference/android/app/Fragment.html#isAdded()
#Override
protected void postExecute(){
if(isAdded()){
//perform Display Changes
}
}
Move your
startNewAsyncTask();
to onActivityCreated()
I'm assuming your using FragmentPagerAdapter with your ViewPager.
To enable smooth animations, the ViewPager by default keeps the current fragment and the two neighbors in resumed state. So onCreateView is not the best place to start the AsyncTask.
Instead you need to create a custom listener interface. The fragments in the ViewPager should implement it, and call the new interface from the ViewPager's OnPageChangeListener.
Check out my answer to this question or you can read the whole tutorial here.
You're getting that exception because you're calling getActivity() too early. You should do it after onActivityCreated() (see this diagram)
Executing of onCreateView() in background is fine and actually is default behaviour. The thing is, ViewPager is optimised to load a content of neighbour non-visible pages in background to improve UX. You can do this:
mViewPager.setOffscreenPageLimit(2); (default value is 1) to load all 3 pages at once (1 is loading as currently visible and other 2 as optimisation). Or set it to 0 to disable this behaviour, but it's not the best idea.
In general, you should cash your loaded data and do not load it again by making your fragment's lifecycle methods as light as possible. Page limit of 2 is fine for 3 pages, but if you'll have for example 10 pages, limit of 9 is too much.
If I've understood your question right, I think you need unique content with each Fragment right?
Try using the varible arguments of the execute method. For example:
yourTask.execute(<some-unique-URL>, parameter, one-more-parameter);
In this way you can pass a unique URL per fragment form which you can get your content.
I feel you already have this. The doInBackground method has the List of URLs. You just need to pass that information in the execute method and utilize it in doInBackground.
Hope this helps!
It is normal that it runs the AsyncTask from the adjacent Fragments, since the ViewPager + PagerAdapter combo, works loading the current, previous and next Fragment.
You should focus the problem not to stop AsyncTask from running, but to let it run w/o throwing a NullPointerException.
The following should be called inside onCreateView()
adapter = new PostItemAdapter (getActivity(), android.R.layout.simple_list_item_1, myList);
setListAdapter(adapter);
And then, onPostExecute()
myList.clear();
myList.addAll(listData);
adapter.notifyDataSetChanged();
The ViewPager will create views for fragments in adjacent pages and destroy views for fragments which are not adjacent to current page. Thus, the onCreateView of page 1 will get called if you navigate from page1->page2->page3->page2. You can have the viewpager keep more pages in memory by using ViewPager.setOffscreenPageLimit.
The fragmentPagerAdapter retains the fragment objects. Only the views are destroyed. Thus, when viewpage recreates page1's view, the fragment object is the same. Hence, all the fields in the fragment will get retained.
As in most applications where there's no realtime data, it is not required not load the data every time the view of the fragment is created, you can store the data in the fragment object after loading. Then, before starting the AsyncTask in onCreateView/onActivityCreated, check if the data has been previously loaded or not.
class PageFragment {
private List<String> mData;
...
void onActivityCreated() {
if (data == null) { // OR if the data is expired
startAsyncTask();
} else {
updateViews();
}
}
void updateViews() {
// Display mData in views
}
class LoadDataTask extends AsyncTask<List<String>, ..., ...> {
...
void onPostExecute(List<String> result) {
PageFragment.this.mData = result;
PageFragment.this.updateViews();
}
}
I recommend that you use loaders for loading data for a fragment. For your purpose, you can configure a loader to load data only once.
This is a great tutorial on Loaders.
http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html
In the tutorial, the loader is configured to return previous data immediately if available, and then fetch data in background and return it after fetching completes. Thus, the UI will get updated after fresh data gets downloaded but at the same time, it will show the previous data initially while the download happens.
You can use another activity - this activity will run asynctask and then move to your fragment related activity. In this way it should call only once.
In case you need to update Fragment UI using this AsyncTask then use a static method to call through AsyncTask.

Creating a gallery by parsing a webpage for meta data

I am trying to retrieve some image URLs and image texts by scraping a website when my app starts up. I then want to use arrays I've created of imageTexts and imageURLs to create a ViewPager so I can display the images as a scrollable gallery.
I execute the network task of fetching the webpage and parsing the HTML as an AsyncTask (downloadTask() in the code below).
My problem is that as this happens in the background it doesn't have time to fetch and parse the data before the code tries to display the images. I get a null pointer error when the code hits
new ViewPagerAdapter(this, getimageText(), getimageURLS())
I can deduce that the array that should eventually store the image URLs and the image Texts hasn't been created yet. This is at the point my app crashes.
Here's my code. I'm new to both Android and Java so clearly I could do with a lot of help!
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the view from viewpager_main.xml
setContentView(R.layout.viewpager_main);
// Fetch the list of URLs using a background network task
new downloadTask().execute(BASE_URL);
// Locate the ViewPager in viewpager_main.xml
viewPager = (ViewPager) findViewById(R.id.pager);
// Pass results to ViewPagerAdapter Class
adapter = new ViewPagerAdapter(this, getimageText(), getimageURLS());
// Binds the Adapter to the ViewPager
viewPager.setAdapter(adapter);
}
You can initially set a default image and you can modify your asyncTask to take in a callback method. This callback method would be a method that initializes your ViewPager. In your AsyncTask you would get the images and override the onPostExecute() method to call that callback method.
Example
class PictureGrabber extends AsyncTask<URL, VOID, VOID>{
IPicCallBack callBack;
PictureGrabber(IPicCallBack callback)
{
this.callback = callback;
}
protected Void doInBackGround(URL... urls){
}
protected void onPostExecute(VOID result)
{
callback.Init();
}
public interface IPicCallBack{
public void Init();
}
Then in your code you need to implement that IPicCallback and set it on the asyncTask and when the task is complete it'll call that method

Improve Activity starting time (AsyncTask?)

I have an Activity with a ViewPager and some Fragments in it.
The problem: It takes ages to start the activity because the ViewPager is created in the onResume methode. 2-3 seconds because of database requests.
I thought I can start a AsyncTask in onResume and do there the heavy work. But the time to start the Activity doesn't decrease.
If I place a Button in the View and do the work in onClick everything works fine. The Activity starts really fast and after the click the heavy work starts.
some code to think about:
The AsyncTask implementation
private class LoadTask extends AsyncTask<Void, Integer, Boolean>{
#Override
protected Boolean doInBackground(Void... params) {
PageAdapter mPageAdapter = new PageAdapter(
getSupportFragmentManager(), mFragments);
mViewPager.setAdapter(mPageAdapter);
return true;
}
}
the task is called in onResume
#Override
protected void onResume() {
super.onResume();
LoadTask task = new LoadTask();
task.execute();
}
This solution doesn't improve the starting time of the activity.
Is there a way to start the Activity and after inflating and displaying everything (progressbar...) doing the heavy task?
What you need to do is setup the empty view pager and link it to the adapter when you create your activity. Then, fill the content into whatever collection backs your adapter in the async task and notify the view that the dataset has changed. The view can't be displayed until it's created and you have moved the registering of the adapter to the async task so you won't notice a difference. The goal is to have your app show some view when you start it and simply fill in the data as it becomes available (as you probably know).

notifyDataSetChanged() on my adapter does not update the listview, why?

I have a activity that extends listactivity, extended in this class i have a class that extends baseadapter.
now in my listactivity i have this onCreate
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new BuildingAdapter(this));
final ProgressDialog loading = new ProgressDialog(this);
loading.setProgressStyle(ProgressDialog.STYLE_SPINNER);
loading.setTitle("Laddar");
loading.setMessage("Hämtar data från servern");
loading.show();
new AsyncTask<Void, Void, DataPacket.Data>()
{
#Override
protected DataPacket.Data doInBackground(Void... params){
return ServerConnection.getBuildings();
}
#Override
protected void onPostExecute(DataPacket.Data param) {
((MainenanceApplication)getApplication()).setDataStructure(param);
loading.dismiss();
//((BuildingAdapter)getListAdapter()).notifyDataSetChanged();
setListAdapter(new BuildingAdapter(BuildingSelectionActivity.this));
}
}.execute();
}
This works as it's supposed to, but my question is in onPostExecute I update the datastructure that the list adapter uses.
Why cant I just call notifyDataSetChanged ??
If I have that line the view does not update itself, but if I use the line under where I do setListAdapter, it all works.
If the adapter is already set, setting it again will not refresh the listview. Instead first check if the listview has a adapter and then call the appropriate method.
I think its not a very good idea to create a new instance of the adapter while setting the list view. Instead, create an object.
BuildingAdapter adapter = new BuildingAdapter(context);
if(getListView().getAdapter() == null){ //Adapter not set yet.
setListAdapter(adapter);
}
else{ //Already has an adapter
adapter.notifyDataSetChanged();
}
Have you tried using an AsyncTaskLoader instead of an AsyncTask for this. It's this kind of stuff that Loaders were exactly designed for. Note that even though Loaders weren't available until API-10 you can still easily access them via the android Support Pacakge from API-4 and up.
The only place you can update the UI is in onProgressUpdate(...);. From your doInBackground(...), call publishProgress(...).

Categories

Resources