0

I'm writing an application that uses bluetooth connection to read some data from COM port over bluetooth. I am using ViewPager to display several Fragments which I use to show some data (each Fragment in a different way). The app has 2 activities:

  1. MainActivity - initialization of the bluetooth connection and managing mentioned fragments
  2. Activity used to search for bluetooth devices

When MainActivity starts, it activates the bluetooth in case it's off and displays first of the fragments. I can scan for devices by pressing the button in the action panel.

My problem is quite simple - I'd like to push some initial data (about connection) to fragments after initialization of the MainActivity, and after making a BT connection I want to push some data (about 10 reads/sec) only to a visible fragment.

I tried to do this by using LocalBroadcastManager in all of the fragments, but the broadcastReceiver is not registered that early and it don't receive all of the messages. I tried to move registration to onCreate in the fragments classes but it didn't work either. I've also tried to implement callback interface to the main activity but I was getting NPE.

Besides that one issue I'm worried it might not be the best solution to the problem of updating one or more fragments about the changes. I'm open to advices.

Mainactivity class:

@Override
protected void onStart() {
    super.onStart();

    if (!bluetoothAdapter.isEnabled()) {
        Intent enableBluetoothIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BLUETOOTH);
    } else {
        if (btService == null) {
            btService = new BluetoothConnectionService(this, msgHandler);
        }
    }
}

private final Handler msgHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        Intent intent = new Intent(STATUS_EVENT);
        switch (msg.what) {
            case MESSAGE_STATE_CHANGE:
                switch (msg.arg1) {
                    case BluetoothConnectionService.STATE_CONNECTED:
                        intent.putExtra("status", "Connected");
                        // tell fragments about the state change
                        break;
                    case BluetoothConnectionService.STATE_CONNECTING:
                        intent.putExtra("status", "Connecting");
                        // tell fragments about the state change
                        break;
                    case BluetoothConnectionService.STATE_NONE:
                        intent.putExtra("status", "Not connected");
                        // tell fragments about the state change
                        break;
                    default:
                        intent.putExtra("status","Unknown state");
                        break;
                }                  
        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
        }
    }
};

BTService part:

public void setCurrentConnectionState(int currentConnectionState) {
    this.currentConnectionState = currentConnectionState;

    msgHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, currentConnectionState, -1).sendToTarget();
}

public BluetoothConnectionService(Context context, Handler handler){
    this.context = context;
    this.msgHandler = handler;
    setCurrentConnectionState(STATE_NONE);
}

One of the fragments:

public class ConnectionStatusFragment extends Fragment {

TextView connectionStatus;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_connection_info, container, false);
    connectionStatus = (TextView) rootView.findViewById(R.id.connectionStatus);
    LocalBroadcastManager.getInstance(getActivity()).registerReceiver(broadcastReceiver, new IntentFilter(MainActivity.STATUS_EVENT));
    return rootView;
}

private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {         
        connectionStatus.setText(intent.getStringExtra("status"));
    }

};

}

Berben
  • 3
  • 2
  • What prevents you from adding a function to the Fragment and passing data to it via the function's parameters? – BVB Jan 03 '14 at 22:31
  • This is actually a pretty complex question because your asking what is the best way to talk to a fragment. I would recommend checking out squareup's otto library. Your fragments can become position aware by adding their position into a bundle and fragment.setArguments(bundle) within the ViewPager.getView().You can then post an event with the position and info you want it to update. Another suggestion would be to set the tag on the fragment. That way you could look it up from the FragmentManager(FragmentSupportManager) and see if it is null or not then update data. – MinceMan Jan 03 '14 at 23:05
  • My warning with the fragmentManager is you can end up with activities and fragments in random states which can really mess you up, otto prevents that. – MinceMan Jan 03 '14 at 23:05
  • Make sure you setup your viewpager before you start messing with the fragments. Again \@Produce and \@Subscribe with otto is the only way I talk to fragments now. http://square.github.io/otto/ – MinceMan Jan 03 '14 at 23:09
  • I read about using FragmentManager to do this, but as you said most of the solutions are quite messy. I think I might be making a mistake by trying to set some data on a fragment that early (before it gets initialized). I'll check the library you mentioned, thanks. – Berben Jan 03 '14 at 23:16
  • As for the ViewPager, I think I'm setting it up properly earlier, but I still get NPE all the time while I try to use it's fragments straight from onCreate or onResume. – Berben Jan 03 '14 at 23:19

1 Answers1

0

Rethink your design along MVC lines and apply it in android to Data-Model which collaborates in MVC fashion with adapters/Views used by Fragments. Adapters inside your fragments are hooked to data-changed events that originate in the model...

In background, off the UI and orthogonal to your fragments, establish your connections and update your Data-Model. No need to pass information about BT connections to your fragments. Why violate a separation of concerns? Its your model that needs to know about BT connections, not your VIEW!

All the connections and IO for updating your data-model are encapsulated in your MODEL actions/processes. Your View does not have to know anything other than "data-model-changed". All that your fragment has to have is the Event(data-has-changed) so that it can fire 'Has-changed' for your adapter -which will then take care of updating the UI in the appropriate fragment.

Applying this practically to android/fragments and guessing a little about your design, i think that the contstructors for your fragments can utilize an interface that allows you to construct the View/Fragment regardless of the state of your actual data-model. At the time of instantiation, the fragment and its adapter do not know everything about the state of the data arriving on the BT connection in a different module. The fragment just needs to use an interface to accommodate the 'model-has-changed' event.

I think you can use the 'looper' and a handler for that collaboration around the event. This might mean that the Model would need a reference to the handler object over in the View/fragment class. That handler is for messages containing state-changes from the model.

module coordination in steps:

  1. construct fragment/View - object exists , view hidden , no data

1.a. construct handler in fragment, creating reference to the receiver object for messages on the looper

2.a. Construct model, including ref to the above handler.

2.b. Do the BT stuff and IO in the model. When 'model-changed', obtain the message whose content signals what your view will need to know about 'model-changed'.

2.c. Send the message which will be received by the handler over in the View layer/fragment (see 1.a.).

Sample:

see very end of answer here to see how the fragment/View receives messages from the looper and then adjusts the UI...

Community
  • 1
  • 1
Robert Rowntree
  • 6,230
  • 2
  • 24
  • 43
  • That's a really helpful answer. I tried to implement it the correct MVC way from the beginning, but I couldn't think of the proper way to follow. In the meantime I managed to solve my issues by using event bus and registering/unregistering my fragments into it. I know it's not the greatest solution, but I have to stick with it for now as other issues had appeared. I'm sure I'll go back to your post in some time when I'll be trying to rework the part of the app with Fragments. Thanks a lot. – Berben Jan 06 '14 at 15:25