I've been trying to debug this thing for hours and I really can't see the issue here.
This is my MainActivity. The main thing to look for here is the ArrayList<Servico>
, being Servico
a custom object. I've created a simple "event" class - ServicoActual
- that has just a Servico
object reference (and a constructor/getter):
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.SearchManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import net.pedromoreira.billper.events.ServicoActual;
import java.util.ArrayList;
import java.util.Locale;
import de.greenrobot.event.EventBus;
public class MainActivity extends Activity {
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private ActionBarDrawerToggle mDrawerToggle;
private CharSequence mDrawerTitle;
private CharSequence mTitle;
private ArrayList<Servico> servicos;
//private String[] mPlanetTitles;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
servicos = new ArrayList<Servico>();
servicos.add(new Servico("Luz"));
servicos.add(new Servico("Água"));
servicos.add(new Servico("Gás"));
//Log.i("onCreate", "Servicos: " + servicos.size());
setContentView(R.layout.activity_main);
mTitle = mDrawerTitle = getTitle();
//mPlanetTitles = getResources().getStringArray(R.array.planets_array);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
// set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// set up the drawer's list view with items and click listener
mDrawerList.setAdapter(new ArrayAdapter<Servico>(this,
R.layout.drawer_list_item, servicos));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
// enable ActionBar app icon to behave as action to toggle nav drawer
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
// ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon
mDrawerToggle = new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */
) {
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
if (savedInstanceState == null) {
selectItem(0);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
/* Called whenever we call invalidateOptionsMenu() */
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// If the nav drawer is open, hide action items related to the content view
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this.
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
// Handle action buttons
switch(item.getItemId()) {
case R.id.action_websearch:
// create intent to perform web search for this planet
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());
// catch event that there's no activity to handle intent
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
Log.i("DrawerItemClickListener", "Position: " + position);
}
}
private void selectItem(int position) {
// update the main content by replacing fragments
Fragment fragment = new ServicoFragment();
//Bundle args = new Bundle();
//args.putInt(ServicoFragment.ARG_PLANET_NUMBER, position);
//fragment.setArguments(args);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
Log.i("selectItem", "Servico: " + servicos.get(position).getNome());
EventBus.getDefault().postSticky(new ServicoActual(servicos.get(position)));
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(servicos.get(position).getNome());
mDrawerLayout.closeDrawer(mDrawerList);
}
@Override
public void setTitle(CharSequence title) {
mTitle = title;
getActionBar().setTitle(mTitle);
}
/**
* When using the ActionBarDrawerToggle, you must call it during
* onPostCreate() and onConfigurationChanged()...
*/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggls
mDrawerToggle.onConfigurationChanged(newConfig);
}
/**
* Fragment that appears in the "content_frame"
*/
public static class ServicoFragment extends Fragment {
//public static final String ARG_PLANET_NUMBER = "planet_number";
private TextView mTestText;
public ServicoFragment() {
// Empty constructor required for fragment subclasses
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault().registerSticky(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_servico, container, false);
//int i = getArguments().getInt(ARG_PLANET_NUMBER);
//String planet = getResources().getStringArray(R.array.planets_array)[i];
//int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),
// "drawable", getActivity().getPackageName());
//((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);
//getActivity().setTitle(planet);
mTestText = ((TextView) rootView.findViewById(R.id.test_text));
Log.i("onCreateView", "mTestText: " + mTestText.toString());
mTestText.setText("xpto");
return rootView;
}
public void onEvent(ServicoActual e){
Servico servico = e.getServico();
Log.i("onEvent", "Servico: " + servico.getNome());
getActivity().setTitle(servico.getNome());
mTestText.setText(servico.getNome());
}
}
}
So, when a Drawer list item is clicked, I'm trying to pass the corresponding Servico
(inside the ServicoActual
"event") to the ServicoFragment
, which should write the Servico
's name to its TextView
.
This is what happens with a single click on the first item (0):
07-13 00:50:35.388 26244-26244/net.pedromoreira.billper I/ViewRootImpl﹕ ViewRoot's Touch Event : Touch Down
07-13 00:50:35.628 26244-26244/net.pedromoreira.billper I/ViewRootImpl﹕ ViewRoot's Touch Event : Touch UP
07-13 00:50:36.308 26244-26244/net.pedromoreira.billper I/ViewRootImpl﹕ ViewRoot's Touch Event : Touch Down
07-13 00:50:36.358 26244-26244/net.pedromoreira.billper I/ViewRootImpl﹕ ViewRoot's Touch Event : Touch UP
07-13 00:50:36.428 26244-26244/net.pedromoreira.billper I/selectItem﹕ Servico: Luz
07-13 00:50:36.428 26244-26244/net.pedromoreira.billper I/onEvent﹕ Servico: Luz
07-13 00:50:36.458 26244-26244/net.pedromoreira.billper E/Event﹕ Could not dispatch event: class net.pedromoreira.billper.events.ServicoActual to subscribing class class net.pedromoreira.billper.MainActivity$ServicoFragment
java.lang.NullPointerException
at net.pedromoreira.billper.MainActivity$ServicoFragment.onEvent(MainActivity.java:235)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at de.greenrobot.event.EventBus.invokeSubscriber(EventBus.java:569)
at de.greenrobot.event.EventBus.postToSubscription(EventBus.java:500)
at de.greenrobot.event.EventBus.postSingleEvent(EventBus.java:475)
at de.greenrobot.event.EventBus.post(EventBus.java:365)
at de.greenrobot.event.EventBus.postSticky(EventBus.java:406)
at net.pedromoreira.billper.MainActivity.selectItem(MainActivity.java:161)
at net.pedromoreira.billper.MainActivity.access$300(MainActivity.java:35)
at net.pedromoreira.billper.MainActivity$DrawerItemClickListener.onItemClick(MainActivity.java:146)
at android.widget.AdapterView.performItemClick(AdapterView.java:299)
at android.widget.AbsListView.performItemClick(AbsListView.java:1158)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:2957)
at android.widget.AbsListView$3.run(AbsListView.java:3849)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5105)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
at dalvik.system.NativeStart.main(Native Method)
07-13 00:50:36.458 26244-26244/net.pedromoreira.billper I/onEvent﹕ Servico: Luz
07-13 00:50:36.468 26244-26244/net.pedromoreira.billper D/Event﹕ No subscribers registered for event class de.greenrobot.event.SubscriberExceptionEvent
07-13 00:50:36.468 26244-26244/net.pedromoreira.billper I/DrawerItemClickListener﹕ Position: 0
07-13 00:50:36.498 26244-26244/net.pedromoreira.billper I/onEvent﹕ Servico: Luz
07-13 00:50:36.508 26244-26244/net.pedromoreira.billper E/Event﹕ Could not dispatch event: class net.pedromoreira.billper.events.ServicoActual to subscribing class class net.pedromoreira.billper.MainActivity$ServicoFragment
java.lang.NullPointerException
at net.pedromoreira.billper.MainActivity$ServicoFragment.onEvent(MainActivity.java:236)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at de.greenrobot.event.EventBus.invokeSubscriber(EventBus.java:569)
at de.greenrobot.event.EventBus.postToSubscription(EventBus.java:500)
at de.greenrobot.event.EventBus.subscribe(EventBus.java:288)
at de.greenrobot.event.EventBus.register(EventBus.java:189)
at de.greenrobot.event.EventBus.registerSticky(EventBus.java:166)
at net.pedromoreira.billper.MainActivity$ServicoFragment.onCreate(MainActivity.java:209)
at android.app.Fragment.performCreate(Fragment.java:1688)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:860)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1063)
at android.app.BackStackRecord.run(BackStackRecord.java:684)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1450)
at android.app.FragmentManagerImpl$1.run(FragmentManager.java:444)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5105)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
at dalvik.system.NativeStart.main(Native Method)
07-13 00:50:36.508 26244-26244/net.pedromoreira.billper D/Event﹕ No subscribers registered for event class de.greenrobot.event.SubscriberExceptionEvent
07-13 00:50:36.508 26244-26244/net.pedromoreira.billper I/onCreateView﹕ mTestText: android.widget.TextView{429216d0 V.ED.... ......ID 0,0-0,0 #7f090003 app:id/test_text}
What can I be doing wrong?
I was wrong. The warning is about nobody being registered at the time of posting. The actual exception probably has to do with the Activity not being attached when the event runs. You probably want onAttach()
.
http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)
Still feels like kind of a weird architecture, but it would be false to say none of my apps have weird architectures.
Update:
Let me start over. My original answer said EventBus was crashing because nobody was registered for your event. However, that log statement isn't a crash. It's just a debug. I have found EventBus to be a bit aggressive with that kind of thing, but on the balance it probably helps.
The NullPointerException is different. When onCreate()
is called on your fragment, it registers to your sticky event. This IMMEDIATELY calls your event. It's right there in the stack. At that point, your fragment is probably attached to your activity, but onCreateView()
has (probably) not been called, so your TextView doesn't exist. I can't see which line is throwing the exception exactly. See the life cycle:
If you want to use a sticky event, you'll need to register later in the Fragment life cycle. I would guess you want onCreateView()
/onDestroyView()
. I've never seen that kind of register/unregister pairing, but it should work.
Generally I think we'd do this with arguments to the fragment, or possibly getting the data from the activity directly, but all methods are fairly clumsy and/or verbose, which is why I'm still an "only when needed" kind of person with the fragments.
An alternative would be to create a Handler in the UI thread and post a Runnable that does the register, which should schedule that after the pending life cycle events, but if you don't need that, don't do it.
Also, you could probably just post/register the Servico
rather than the ServicoActual
, but that shouldn't make any difference to your issue.