1

I'm trying to create an app with 3 tabs, with each tab representing data that I receive from another device. I receive this data every 5 mins or so, so I need the text in the tabs to change constantly. However, I have trouble updating the TextView in the tabs to match the latest incoming data, as it causes a NullPointerException. Below is the main activity in which lies the issue:

public class PlantStatsActivity extends FragmentActivity {

public DrawerLayout mDrawerLayout;
public ListView mDrawerList;
private TabLayout tabLayout;
private ViewPager viewPager;
private ViewPageAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_plant_stats);
    initPubNub();


    tabLayout = (TabLayout) findViewById(R.id.tab_layout);
    tabLayout.addTab(tabLayout.newTab().setText("Moisture"));
    tabLayout.addTab(tabLayout.newTab().setText("Light"));
    tabLayout.addTab(tabLayout.newTab().setText("Temperature"));

    viewPager = (ViewPager) findViewById(R.id.pager);
    adapter = new ViewPageAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
    viewPager.setAdapter(adapter);

    viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));

    tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {

        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            viewPager.setCurrentItem(tab.getPosition());
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {

        }

        @Override
        public void onTabReselected(TabLayout.Tab tab) {

        }
    });

    LightFragment lightFragment = (LightFragment) adapter.getItem(1);
    lightFragment.setLightText("Test"); //This is where the problem lies.
}

I get a NullPointerException because the field lightTextis null (I'm just trying to get this to work on the "Light" tab, for now).

This the the fragment who's TextView I'm trying to update in the activity

public class LightFragment extends Fragment {
    private int lightLevel;
    private TextView lightText;

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v = inflater.inflate(R.layout.fragment_light, container, false);
        lightText = (TextView) v.findViewById(R.id.current_level_light);
        return v;
    }

    public int getLightLevel() {
        return lightLevel;
    }

    public void setLightLevel(int newLightLevel){
        lightLevel = newLightLevel;
    }

    public TextView getLightText() {
        return lightText;
    }

    public void setLightText(String newText) {
        lightText.setText(newText); //Method used in activity causing crash.
    }

}

I'm including the ViewPageAdapter class:

public class ViewPageAdapter extends FragmentStatePagerAdapter {
    int mNumOfTabs;

    public ViewPageAdapter(FragmentManager fm, int numOfTabs){
        super(fm);
        this.mNumOfTabs = numOfTabs;
    }

    @Override
    public Fragment getItem(int position){
        switch (position){
            case 0:
                MoistureFragment tab1 = new MoistureFragment();
                return tab1;
            case 1:
                LightFragment tab2 = new LightFragment();
                return tab2;
            case 2:
                TemperatureFragment tab3 = new TemperatureFragment();
                return tab3;
            default:
                return null;
        }
    }

    @Override
    public int getCount(){
        return mNumOfTabs;
    }

}

Code that shows the process of receiving and handling data, which is a JSON object that stores an int value I'm interested in. Called at the beginning of the main activity.

public void initPubNub(){
    PNConfiguration pnConfiguration = new PNConfiguration();
    pnConfiguration.setPublishKey(PUBLISH_KEY);
    pnConfiguration.setSubscribeKey(SUBSCRIBE_KEY);
    pnConfiguration.setUuid("AndroidPiLight");
    mPubNub = new PubNub(pnConfiguration);
    mPubNub.subscribe().channels(Arrays.asList(CHANNEL)).execute();
    Log.d(TAG, "subscribed");

    mPubNub.addListener(new SubscribeCallback() {
        @Override
        public void status(PubNub pubnub, PNStatus status) {
            // handle any status
        }

        @Override
        public void message(PubNub pubnub, PNMessageResult message) {
            JsonNode lightNode = message.getMessage().findValue("lightValue"); // "lightValue" is JSON key.
            int newLight = lightNode.asInt();
            Log.d(TAG, "Got message as int");
            Bundle data = new Bundle();
            data.putInt("lightMessage", newLight);
            Message message1 = new Message();
            message1.setData(data);
            android.os.Handler handler = LightFragment.sUpdateHandler;
            if (handler != null){
                handler.sendMessage(message1);
            }

I'm a beginner to Android so I'm not really sure if I'm doing something wrong in the app, or if I need to add something totally different to make this work. I've looked thoroughly through previous questions of Stack Overflow, and could not find any answers. This is my first question on Stack Exchange, and any feedback is sincerely appreciated.

Miki P
  • 652
  • 1
  • 9
  • 22
  • where are you calling setLightText() funciton ? – Khizar Hayat Jun 29 '16 at 06:39
  • you can pass data from pageradapter using bundle.. – Nitesh Jun 29 '16 at 06:42
  • 1) you get a NullPointerException because getItem() always returns a fresh instance of the fragment. This instance has not been added to the UI and so onCreateView() has not been called. 2) It's difficult to access the fragments used for a TabLayout. Write some kind of interface which the activity/ the fragment has to implement. Which way is best depends on your situation: how often/ from which source/ triggered by which event does the fragment UI have to be updated – Bö macht Blau Jun 29 '16 at 10:14
  • @KhizarHayat In the the PlantStatsActivity class, last line. – Miki P Jun 29 '16 at 16:33
  • @0X0nosugar I'm getting the data from the cloud (real-time service called PubNub), which I set up in the main (PlantStats) activity. This is triggered by a Raspberry Pi, every few minutes. Is there a specific interface I should be using for this? I've looked at https://developer.android.com/training/basics/fragments/communicating.html#DefineInterface , but I'm not sure if that's an ideal way to go about this. It might be, but as I said, I have little experience in Android. – Miki P Jun 29 '16 at 21:54
  • Maybe this [SO post](http://stackoverflow.com/questions/18088076/update-fragment-from-viewpager) (specifically the accepted answer and the sample in the last answer) will be helpful. Other approach: use LocalBroadcastManager, put a BroadcastReceiver in each Fragment and have the Activity send the updates as soon as it receives them *as well as* hand them over to the adapter which will use the data when instantiating a new Fragment (if getItem() is called) . For this last part, consider a solution like @Nitesh Kumar suggested: use setArguments() for the fragment. – Bö macht Blau Jun 30 '16 at 05:36
  • If you're interested in the LocalBroadcastManager approach, I'd like to know what kind of data you need to transmit for the three fragments. – Bö macht Blau Jun 30 '16 at 05:38
  • @0X0nosguar I've tried the approach in the last answer, and it worked (I've edited my answer to include this code). It seems like an ok solution, but I'd appreciate feedback from more experienced people. I've also included the code that shows how I receive my data. – Miki P Jun 30 '16 at 17:49
  • sorry, was not notified of your comment because you misspelled my nickname. I think there are several ways to communicate with the Fragment. If the Fragment UI is "cheap" (does not take too long to set up), I think it's ok to just create a new instance every time the data has to be updated. If on the other hand the Fragment UI is more complex, then maybe I'd prefer to somehow notify the Fragment that it should fetch the new data from some kind of Observer/ Mediator. For Fragments which are currently not being shown, it will be necessary anway to somehow store the data until they are needed. – Bö macht Blau Jul 06 '16 at 14:59

1 Answers1

0

Try this in your pager adapter class do this

       LightFragment tab2 = new LightFragment();
        Bundle data = new Bundle();
        data.putInt("current_page", "id");
        data.putString("current_id","" );
        data.putInt("sub_cat_id","");
        myFragment.setArguments(data);

Then in your LightFragment class onCreate get this bundle value

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /** Getting the arguments to the Bundle object */
    Bundle data = getArguments();

    /** Getting integer data of the key current_page from the bundle */
    mCurrentPage = data.getInt("current_page", 0);

    }
Nitesh
  • 318
  • 3
  • 16
  • This works when I put a test int, such as 123. However, in the app, I'm getting a value from the cloud (a real-time service called PubNub), which I initialize in PlantStatsActivity. Should the ViewPageAdapter class be doing things like handling values I receive in this activity from PubNub? I have a feeling this would tightly couple the two classes, and may cause problems in the future. What do you think? – Miki P Jun 29 '16 at 21:50
  • instead of creating different pageradaper class merge adapter class with activity class and this will solve your problem m sure – Nitesh Jun 30 '16 at 05:14