0

I am listening to network activity in an android application. When events come over the network, I would like to update some TextViews that reside inside of different Fragments in my application. Right now I have a single screen application where one Fragment is on the screen at a time.

How can I call a method on an existing Fragment inside of a ViewPager, managed by a FragmentStatePagerAdapter?

I am able to "paint" the UI correctly, it gets onscreen as expected. That all works. What I am having a tough time doing is calling a method when a background thread sends a message to my containing Activity. The thread is long running and will updated each TextView frequently (think of a countdown timer).

{thread} -> Actvity.handleMessage() -> {current}Fragment.update();

I am attempting to discern what the "current", onscreen Fragment is so that I can call a method on it to update its TextViews. I am trying to use the findFragmentByTag approach I have read about, but it is always returning a null Fragment object.

Specifically I am doing this:

private String getFragmentTag(int viewPagerId, int fragmentPosition){
     return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}

//Take the data from the thread (network) and react to it.
@Override
public boolean handleMessage(Message msg) {
    //tell the fragment to update itself        
    //Log.v(TAG, "Handler: " + msg.what);

    int currentFragmentIndex = pager.getCurrentItem();
    Log.v(TAG, "Current Fragment Index: " + Integer.toString(currentFragmentIndex));

    String fTag = getFragmentTag(pager.getId(), currentFragmentIndex );
    Log.v(TAG, "Current Fragment Tag: " + fTag);

    ////THIS IS WHERE I AM HAVING TROUBLE!////
    Log.v(TAG, "fragment: " + getSupportFragmentManager().findFragmentByTag(fTag));
    ////THIS ALWAYS OUTPUTS:
    ////fragment: null



    //now do the update
    //currentFragment.update(sensorDataMap);

    return true;
}

Based on reading a ton and specifically this post - How to get existing fragments when using FragmentPagerAdapter

Here is the relevant code...

MainActivity.java

public class MainActivity extends FragmentActivity implements Callback {
    //receive messages from the other thread
    private  Handler handler = new Handler(this);

    //the runnable listening to the network
    private UDPListener udpl;

    //we will parse the handler data into this map and store the values by int keys
    private SparseArray<String> sensorDataMap = new SparseArray<String>();

    //for logging and convenience
    private static final String TAG = "BRRT";   

    //set up the fragment manager
    private ViewPager pager;
    private ScreenFragmenStatePagerAdapter fragmentManager;
    private ScreenFragment currentFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //remove the titlebar
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //keep the screen on while we are running
        this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        //set up the pager to move through the fragments
        pager = (ViewPager) findViewById(R.id.viewPager);
        fragmentManager = new ScreenFragmenStatePagerAdapter(getSupportFragmentManager());
        pager.setAdapter(fragmentManager);

        //start up the thread
        Log.v(TAG, "start thread");
        startUDPListener(handler);
        Log.v(TAG, "past start thread");    
    }


    //start up the thread to listen to the network, pass in the handle to this thread for messages
    private void startUDPListener(Handler handler){
        //spawn the listener thread, pass in the context and the handler object
        try{
            udpl = new UDPListener(this.getApplicationContext(), handler, sensorDataMap);

            //TODO remove this debug flag
            //DEBUG
            udpl.setDebug(true);
            //DEBUG 

            //set up the threaded reading, event throwing
            Thread reader = new Thread(udpl);
            reader.start();

        } catch(Exception e){
            //TODO real logging
            Log.v(TAG, "caught after trying to start thread");
            Log.v(TAG, e.getMessage());
        }

    }

    //fragile, as this depends on the current naming convention for these fragment IDs in support/v4.
    private String getFragmentTag(int viewPagerId, int fragmentPosition){
         return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

    //Take the data from the thread (network) and react to it. This will get back unparsed strings
    //and have to parse them in this thread, with the UI
    @Override
    public boolean handleMessage(Message msg) {
        //tell the fragment to update itself        
        //Log.v(TAG, "Handler: " + msg.what);

        int currentFragmentIndex = pager.getCurrentItem();
        Log.v(TAG, "Current Fragment Index: " + Integer.toString(currentFragmentIndex));

        String fTag = getFragmentTag(pager.getId(), currentFragmentIndex );
        Log.v(TAG, "Current Fragment Tag: " + fTag);


        Log.v(TAG, "fragment: " + getSupportFragmentManager().findFragmentByTag(fTag));
        //Log.v(TAG, "Current Fragment Null? " + (currentFragment == null));


        //now do the update
        //currentFragment.update(sensorDataMap);

        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

ScreenFragmenStatePagerAdapter .java

public class ScreenFragmenStatePagerAdapter extends FragmentStatePagerAdapter {
    //the set of screens we are interested in.
    private ArrayList<ScreenConfig> screens = new ArrayList<ScreenConfig>(6);

    //for logging and convenience
    private static final String TAG = "CC";

    //convenience variable to let us know what screen we are pointing at, logically
    private ScreenConfig currentScreenConfig;

    public ScreenFragmenStatePagerAdapter(FragmentManager fm) {
        super(fm);

        //set up the data.
        setupScreenData();
    }


    @Override
    public Fragment getItem(int pos) {
        //Log.v(TAG, "fragment pos: " + pos);   

        //hopefully this will always work
        currentScreenConfig = screens.get(pos);
        ScreenFragment sf;

        if (currentScreenConfig.eventCount() == 1){
            sf = OneUpScreen.newInstance();
        } else {
            sf = FourUpScreen.newInstance();
        }

        sf.setConfig(currentScreenConfig);
        return sf;
    }


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


    //class that maps the various events to their descriptions in a screen by screen way
    //do all the grunt work of mapping screens to events
    private void setupScreenData(){
        //TODO convert this stuff to JSON so we can pass them around in Bundles

        //a 1up screen
        ScreenConfig configGun = new ScreenConfig("Gun");
        configGun.addEvent(204, "TimeToGun","Ttg", "TTG");
        screens.add(configGun);

        //the rest are 4up screens
        ScreenConfig configBowman = new ScreenConfig("Bowman");
        configBowman.addEvent(5, "ExTws","Tws", "TWS");
        configBowman.addEvent(34, "ExLayTimeOnStrb", "LayTimeOnStrb", "Time Stb");
        configBowman.addEvent(37, "ExLayTimeOnPort", "LayTimeOnPort", "Time Prt");
        configBowman.addEvent(113, "ExNextMarkTwa", "NextMarkTwa", "TWA");
        screens.add(configBowman);

        ScreenConfig configPit = new ScreenConfig("Pit");
        configPit.addEvent(88, "ExMarkTime","MarkTime", "Time Mark");
        configPit.addEvent(113, "ExNextMarkTwa", "NextMarkTwa", "Next TWA");
        configPit.addEvent(111, "ExNextMarkRng", "NextMarkRng", "Next Range");
        configPit.addEvent(112, "ExNextMarkBrg", "NextMarkBrg", "Next Brg");        
        screens.add(configPit);

        ScreenConfig configUpwindTrimmer = new ScreenConfig("Upwind Trimmer");
        configUpwindTrimmer.addEvent(5, "ExTws","Tws", "TWS");
        configUpwindTrimmer.addEvent(1, "ExBsp", "Bsp", "BSP");
        configUpwindTrimmer.addEvent(54, "ExTargBspN", "TargBspN", "Tgt BSP");
        configUpwindTrimmer.addEvent(4, "ExTwa", "Twa", "TWA");     
        screens.add(configUpwindTrimmer);   

        ScreenConfig configDownwindTrimmer = new ScreenConfig("Downwind Trimmer");
        configDownwindTrimmer.addEvent(5, "ExTws","Tws", "TWS");
        configDownwindTrimmer.addEvent(4, "ExTwa", "Twa", "TWA");
        configDownwindTrimmer.addEvent(53, "ExTargTwaN", "TargTwaN", "Tgt BSP");
        configDownwindTrimmer.addEvent(58, "ExPolarBspPercent", "PolarBspPercent", "P Bsp %");      
        screens.add(configDownwindTrimmer);

        ScreenConfig configHelmsman = new ScreenConfig("Helmsman");
        configHelmsman.addEvent(5, "ExTws","Tws", "TWS");
        configHelmsman.addEvent(1, "ExBsp", "Bsp", "BSP");
        configHelmsman.addEvent(6, "ExTwd", "Twd", "TWD");
        configHelmsman.addEvent(4, "ExTwa", "Twa", "TWA");      
        screens.add(configHelmsman);

    }
}

activty_main.xml

<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/viewPager"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" />
Community
  • 1
  • 1
JuddGledhill
  • 149
  • 1
  • 1
  • 11

1 Answers1

0

Ok, I gave up on the normal way and moved to an architecture based on EventBus by green robot.

Here are the relevant java files to help you see where I ended up on this issue. I basically moved to posting to an EventBus in my network thread and receiving those events in my Fragments directly. It simplified my code greatly. There is plenty of optimization to do on the code (things like pulling Fragment and Activity lifecycle handlers into a central class or interface) but that is in the future.

Here is the MainActivity.java file. Now all it does (essentially) is set up the viewpager and kick off the network thread.

public class MainActivity extends FragmentActivity {
    //the runnable listening to the network
    private UDPListener udpl;

    //for logging and convenience
    private static final String TAG = "BRRT";   

    //set up the fragment manager
    private ViewPager pager;
    private ScreenFragmenStatePagerAdapter fragmentManager;

    //we will parse the handler data into this map and store the values by int keys
    private SparseArray<String> sensorDataMap = new SparseArray<String>();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //remove the titlebar
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //keep the screen on while we are running
        this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        //set up the pager to move through the fragments
        pager = (ViewPager) findViewById(R.id.viewPager);
        fragmentManager = new ScreenFragmenStatePagerAdapter(getSupportFragmentManager());
        pager.setAdapter(fragmentManager);
    }

    @Override
    public void onResume(){

        //start up the thread
        Log.v(TAG, "start thread");
        startUDPListener();
        Log.v(TAG, "past start thread");    

        super.onResume();
    }


    //start up the thread to listen to the network, pass in the handle to this thread for messages
    private void startUDPListener(){
        //spawn the listener thread, pass in the context and the handler object
        try{
            udpl = new UDPListener(this.getApplicationContext(), sensorDataMap);

            //TODO remove this debug flag
            //DEBUG
            udpl.setDebug(true);
            //DEBUG 

            //set up the threaded reading, event throwing
            Thread reader = new Thread(udpl);
            reader.start();

        } catch(Exception e){
            //TODO real logging
            Log.v(TAG, "caught after trying to start thread");
            Log.v(TAG, e.getMessage());
        }

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

Here is a Fragment. All it does is configure its view, and listen for events on the EventBus.

public class OneUpScreen extends ScreenFragment {   
    private Button name;
    private TextView c00Title, c00Data;

    //DEBUG
    public String title;

    //DEBUG
    private int counter = 0;

    //for logging and convenience
    private static final String TAG = "BRRT";   


    @Override
    public void onResume(){
        EventBus.getDefault().register(this);
        super.onResume();
    }

    @Override
    public void onDestroy(){
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }

    //process the bus messaging
    public void onEventMainThread(ExpeditionEvent event){      <-- READ IT FROM THE BUS
        Log.v(TAG, "OneUp event received: " + event.getEventId() + " : " + event.getEventScreenValue());
        c00Data.setText(""+counter);
        counter++;
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.one_up_screen_layout, container, false);

        name = (Button) v.findViewById(R.id.lblTitle);
        c00Title = (TextView) v.findViewById(R.id.c00Title);
        c00Data = (TextView) v.findViewById(R.id.c00Data);

        Bundle bundle = this.getArguments();

        //initial set up of the text on the screen
        //null check
        name.setText((bundle.getString("name") == null) ? "error" : bundle.getString("name"));
        c00Title.setText((bundle.getString("c00Title") == null) ? "error" : bundle.getString("c00Title"));
        title = bundle.getString("c00Title");

        return v;
    }

    public OneUpScreen(){

    }

    public static OneUpScreen newInstance() {
        OneUpScreen frag = new OneUpScreen();
        return frag;
    }

    //attach the config for this instance.
    @Override
    public void setConfig(ScreenConfig sc){
        //set up the data to paint the screen for the first time
        Bundle b = new Bundle();

        //now we have to parse some stuff into a bundle and send the bundle to the fragment
        b.putString("name", sc.getName());
        b.putString("c00Title", sc.getEvents().get(0).getCleanName());
        b.putInt("c00Data", sc.getEvents().get(0).getEventID());

        //pass it along
        this.setArguments(b);
    }

}

Here is what I call in the network listener, this is in the run) method of the Runnable...

//throw out an event for each item
EventBus.getDefault().post(new ExpEvent(key, value));   <-- PUT IT ON THE BUS

Ta-DA!!!

I hope this will help the next person who is having trouble with ViewPager, FragmentStatePagerAdapters, Fragments or threading in Android.

JuddGledhill
  • 149
  • 1
  • 1
  • 11