1

[My condition]
Now in MainActivity, I create some Fragments in which there are some TextViews. And there's an asynchronous thread AsyncTask in MainActivity to obtain information from web. After AsyncTask finished obtaining it will update the text in TextViews mentioned above through callback method (The Fragment implements a OnInfoGotInterface and instanciate a method onInfoGot() in the interface. onInfoGot() will call the method setTextView() defined in the Fragment to update information).

[My problem]
When execute the program, I found that the time point when AsyncTask finishes get information from web precedes the time point when Fragment call onCreateView(). In another word, when AsyncTask call (OnInfoGotInterface)fragment.onInfoGot() to set the TextViews, the TextViews have not been instanciated yet (TextViews are instanciated in on CreateView() by rootView.findViewById() method). As a result, a NullPointerException showed up.

[I need a resolution]
Now I want to do like this: When AsyncTask finishes getting info from web and is going to call onInfoGot(), we stop it, and make it wait until the Fragment finish onCreateView(). After that we waken the AsyncTask and allow it update the fragment.

PS. Some suggest that I should call new AsyncTask.excute() after onCreateView() in the definition of Fragment. But here AsyncTask and Fragment are both created in MainActivity. They are two differnt task threads in MainActivity, one is used for showing data and the other is used for getting data from Web.

Could anyone give some advice? I would really appreciate it! Here is the code:

MainActivity.java:

public class MainActivity extends Activity{
private List<City> cityList;
private ViewPager mPager;  /*ViewPager for show the page of city info*/
private ScreenSlidePagerAdapter mPagerAdapter;  /* Adapter for the ViewPager, 
        * ScreenSlidePagerAdapter is a subclass of FragmentStatePagerAdapter*/
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_show_city);

    cityList = new ArrayList<City>();
    mPager = (ViewPager) findViewById(R.id.pager);
    mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);
    init();
}

private void init(){
    loadCities();  //Load cities from database into cityList
    initFragments();
    getCityInfo();
}

private void initFragments(){
    mPagerAdapter.removeAllFragments();
    for(City city: cityList){
        PageFragment fragment = new PageFragment(city.getName());
        mPagerAdapter.addFragment(fragment);
        mPagerAdapter.notifyDataSetChanged();
    }
}

private void getCityInfo(){
    for(City city: cityList){
        String cityName = city.getName(); 
        obtainCityInfo(cityName);  
    }
}

private obtainCityInfo(String cityName){
    String request = "http://example.abc.com/"
            + cityName + "&output=json";
    new AccessWebServiceTask().execute(request, cityName); 
}

private class AccessWebServiceTask extends AsyncTask<String, Void, CityInfo>{
    @Override
    protected CityInfo doInBackground(String... urls) {
        String result = getWebContent(urls[0]);  /*Access web through HTTP*/
        String cityName = urls[1];
        /*Transform the String result to a CityInfo object containing information of a city*/
        CityInfo cityInfo = encodeJason(result, cityName);  

        return cityInfo;
    }

    protected void onPostExecute(CityInfo cityInfo){
            OnCityGot(cityInfo); 
    }
}

public void onCityGot(CityInfo cityInfo){
    if(cityInfo != null){  
        String cityName = cityInfo.getCityName();
        /*Set the info field of a city object*/
        cityList.getByName(cityName).setCityInfo(cityInfo);
        mPagerAdapter.updateFragment(cityInfo);
    }
}

}

ScreenSlidePagerAdapter.java

public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter{
    private List<PageFragment> fragmentsList;
    public ScreenSlidePagerAdapter(FragmentManager fm) {
        super(fm);
        fragmentsList = new ArrayList<PageFragment>();
    }

    public ScreenSlidePagerAdapter(FragmentManager fm, List<PageFragment> list){
        super(fm);
        fragmentsList = list;
    }

    @Override
    public PageFragment getItem(int position) {
        return fragmentsList.get(position);
    }

    @Override
    public int getCount() {
        return fragmentsList.size();
    }

    public void setFragmentsList(List<PageFragment> fragmentsList){
        this.fragmentsList = fragmentsList;
        notifyDataSetChanged();
    }

    public void addFragment(PageFragment f){
        fragmentsList.add(f);
        notifyDataSetChanged();
    }

    public void removeFragment(int position){
        fragmentsList.remove(position);
        notifyDataSetChanged();
    }

    public void removeAllFragments(){
        fragmentsList.clear();
        notifyDataSetChanged();
    }

    private PageFragment findFragmentByName(String cityName){
        for(PageFragment fragment: fragmentsList){
            if(fragment.getCityName().equals(cityName))
                return fragment;
        }
        return null;
    }
    public void updateFragment(CityInfo cityInfo){
        String cityName = cityInfo.getCityName();
        OnCityInfoChanged fragment = (OnCityInfoChanged)findFragmentByName(cityName);
        String population = cityInfo.getPopulation();
        fragment.onCityInfoChanged(population);
        notifyDataSetChanged();
    }
}

PageFragment.java:

public class PageFragment extends Fragment implements OnCityInfoChanged{
    private TextView cityNameText;
    private TextView populationText;
    String cityName;
    String population;

    public PageFragment(){}

    public PageFragment(String cityName, String population){
        this.cityName = cityName;
        this.population = population
    }

    public PageFragment(CityInfo cityInfo){
        this.cityName = cityInfo.getCityName();
        this.population = cityInfo.getPopulation();
    }

    public PageFragment(String cityName){
        this.cityName = cityName;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState){
         ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.fragment_city_page2, container, false);
         cityNameText = (TextView)rootView.findViewById(R.id.city_name);
         populationText = (TextView)rootView.findViewById(R.id.population);

         setCityName(cityName);
         setPopulation(population)

         return rootView;
    }

    public void setCityName(String name){
        cityNameText.setText(name);
    }

    public void setPopulation(String population){
        populationText.setText(population);
    }

    public String getCityName(){
        return cityName;
    }  

    @Override
    public void onCityInfoChanged(String population) {
        //setCityName(cityName);
        setPopulation();
    }
}
Chuan Liu
  • 25
  • 1
  • 6

3 Answers3

0

You can try ConditionVariable, it can be used to hold AsyncTask

private ConditionVariable mCondition = new ConditionVariable(false);
mCondition.block(); //used to block
// your condition
mCondition.open(); // used to open
Muthu
  • 1,022
  • 1
  • 7
  • 17
  • It seems it cannot fix the problem. I define mCondition in MainActivity. I call mCondition.block() in onPostExecute() before OnCityGot() is called. And call mCondition.open() in onCreateView() before the return statement. But the result is: the program isn't responding. – Chuan Liu Aug 28 '14 at 09:00
  • Declare the condition variable as volatile since it is shared by many threads. – Dimitri Aug 28 '14 at 09:06
  • Sorry, I made a mistake just now. The result is as before(no any difference) rather than no respond. – Chuan Liu Aug 28 '14 at 09:13
  • Sincerely sorry... Your resolution seems to be a correct one. Thank you! – Chuan Liu Aug 28 '14 at 09:50
  • no need for sorry. I am glad that i was able to help you! – Muthu Aug 28 '14 at 09:54
0

First, you shouldn't use Fragment constructors with parameters. You should send the arguments to the fragment in bundle set to the fragments arguments. See: https://stackoverflow.com/a/15392591/360211

I would have the fragment call the obtainCityInfo method in it's onCreate. And no more lookups then, I can pass the OnCityInfoChanged to the async task for it to call on the onPostExecute.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState){
     ViewGroup rootView = (ViewGroup) inflater.inflate(
            R.layout.fragment_city_page2, container, false);
     cityNameText = (TextView)rootView.findViewById(R.id.city_name);
     populationText = (TextView)rootView.findViewById(R.id.population);

     setCityName(cityName);
     obtainCityInfo(cityName, this);

     return rootView;
}

private obtainCityInfo(String cityName, OnCityInfoChanged callback){
    String request = "http://example.abc.com/"
            + cityName + "&output=json";
    new AccessWebServiceTask(callback).execute(request, cityName); 
}
Community
  • 1
  • 1
weston
  • 54,145
  • 21
  • 145
  • 203
0

I would advise to remove the AsyncTask AccessWebServiceTask from the MainActivity and put it in the Fragment PageFragment. And then in this fragment, override onActivityCreated, start the AsyncTask.

[EDIT]

Here is an update version of your code. Test it if it works :

MainActivity

    public class MainActivity extends Activity{
    private List<City> cityList;
    private ViewPager mPager;  /*ViewPager for show the page of city info*/
    private ScreenSlidePagerAdapter mPagerAdapter;  /* Adapter for the ViewPager, 
            * ScreenSlidePagerAdapter is a subclass of FragmentStatePagerAdapter*/

     protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_show_city);

        cityList = new ArrayList<City>();
        mPager = (ViewPager) findViewById(R.id.pager);
        mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
        mPager.setAdapter(mPagerAdapter);
        init();
    }

    private void init(){
        loadCities();  //Load cities from database into cityList
        initFragments();

    }

    private void initFragments(){
        mPagerAdapter.removeAllFragments();
        for(City city: cityList){
            PageFragment fragment = PageFragment.newFragment(city.getName());
            mPagerAdapter.addFragment(fragment);       
        }
    }
}

ScreenSlidePagerAdapter :

    public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter{
    private List<PageFragment> fragmentsList;
    public ScreenSlidePagerAdapter(FragmentManager fm) {
        super(fm);
        fragmentsList = new ArrayList<PageFragment>();
    }

    public ScreenSlidePagerAdapter(FragmentManager fm, List<PageFragment> list){
        super(fm);
        fragmentsList = list;
    }

    @Override
    public PageFragment getItem(int position) {
        return fragmentsList.get(position);
    }

    @Override
    public int getCount() {
        return fragmentsList.size();
    }

    public void setFragmentsList(List<PageFragment> fragmentsList){
        this.fragmentsList = fragmentsList;
        notifyDataSetChanged();
    }

    public void addFragment(PageFragment f){
        fragmentsList.add(f);
        notifyDataSetChanged();
    }

    public void removeFragment(int position){
        fragmentsList.remove(position);
        notifyDataSetChanged();
    }

    public void removeAllFragments(){
        fragmentsList.clear();
        notifyDataSetChanged();
    }    
}

PageFragment :

    public class PageFragment extends Fragment {
    private TextView cityNameText;
    private TextView populationText;
    private String cityName;
    private String population;

    public static final String CITY_NAME_KEY = "cityname"; 

    public PageFragment(){}


    public static PageFragment newFragment(String cityName){
      PageFragment fragment = new PageFragment();
      Bundle args = new Bundle();
      args.putString(CITY_NAME_KEY, cityName);
      fragment.setArguments(args);
      return fragment;
    }

    @Override
    public void onCreate (Bundle savedInstanceState){
       super.onCreate();
       if(savedInstanceState != null){
         this.cityName = savedInstanceState.getString(CITY_NAME_KEY);
       }       
    }   

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState){
         ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.fragment_city_page2, container, false);
         cityNameText = (TextView)rootView.findViewById(R.id.city_name);
         populationText = (TextView)rootView.findViewById(R.id.population);

         return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState){
       if(this.cityName != null){
         String request = "http://example.abc.com/" + cityName + "&output=json";
         new AccessWebServiceTask().execute(request, cityName); 
       }
    }

    public void setCityName(String name){
        cityNameText.setText(name);
    }

    public void setPopulation(String population){
        populationText.setText(population);
    }

    public String getCityName(){
        return cityName;
    }  


    private class AccessWebServiceTask extends AsyncTask<String, Void, CityInfo>{
    @Override
    protected CityInfo doInBackground(String... urls) {
        String result = getWebContent(urls[0]);  /*Access web through HTTP*/
        String cityName = urls[1];
        /*Transform the String result to a CityInfo object containing information of a city*/
        CityInfo cityInfo = encodeJason(result, cityName);  

        return cityInfo;
    }

    protected void onPostExecute(CityInfo cityInfo){
            OnCityGot(cityInfo); 
     }
   }

  public void onCityGot(CityInfo cityInfo){
     if(cityInfo != null){  
        String population = cityInfo.getPopulation();
        setPopulation(population);
     }
  }
}
Dimitri
  • 8,122
  • 19
  • 71
  • 128