Android Question

Not getting stacktrace when android app crashes

I am making an android app that communicates with a json rest api to provide the user with movie and tv show details.

Currently, I am testing the app (manually) to fix bugs when the app is offline.

I am getting a strange bug when I click on an item in the main recyclerview and the details activity is launched. The Android monitor only shows the following:

09-13 13:39:43.860 30608-30608/<MY-PACKAGE-NAME> E/AndroidRuntime: FATAL EXCEPTION: main
Process: <MY-PACKAGE-NAME>, PID: 30608


It doesn't print a stacktrace, so I don't have a starting point from which I can start to debug.

I know that the crash happens AFTER the details activity is launched, as I set several breakpoints to try and identify when it happens.

The crash happens AFTER the onstart() callback of my details fragment finishes.

How can I get a complete stacktrace?

Also, if there is a glaring error that I don't see, could you point it out for me please?

This is my details activity:

public class DetailActivity extends SearchBaseActivity
implements NestedScrollView.OnScrollChangeListener, FragmentHandler {

private static final String RESOURCE = "resource";

@BindView(R.id.detail_view_container)
NestedScrollView mContainer;
@BindView(R.id.toolbar)
Toolbar mToolbar;
@BindView(R.id.reviews_view_container)
FrameLayout mReviewsContainer;

private Drawable mToolbarBackgroundDrawable;
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private int mScrollFadeLimit;


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

setContentView(R.layout.activity_detail);
ButterKnife.bind(this);

Media media = getIntent().getParcelableExtra(RESOURCE);

mToolbarBackgroundDrawable = mToolbar.getBackground();

setSupportActionBar(mToolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(null);
}

getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
mScrollFadeLimit = ViewUtil.setHeightForAspectRatio(mDisplayMetrics.widthPixels, ViewUtil.STANDARD);

mContainer.setOnScrollChangeListener(this);
MediaDetailFragment.insertOnTarget(this, R.id.detail_view_container, media);
}

@Override
protected void onStart() {
super.onStart();
int newAlpha = getTransparencyRatio(mContainer.getScrollY());
updateActionBarTransparency(newAlpha);
}

@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
int newAlpha = getTransparencyRatio(scrollY);
updateActionBarTransparency(newAlpha);

}

public static Intent getStartIntent(Context context, Media media) {
return new Intent(context, DetailActivity.class).putExtra(RESOURCE, media);
}

private int getTransparencyRatio(int scrollY) {
int headerHeight = mScrollFadeLimit - mToolbar.getHeight();
float ratio = 0;
if (scrollY > 0 && headerHeight > 0)
ratio = (float) Math.min(Math.max(scrollY, 0), headerHeight) / headerHeight;

return (int) (ratio * 255);
}

private void updateActionBarTransparency(int scrollRatio) {
mToolbarBackgroundDrawable.mutate().setAlpha(scrollRatio);
mToolbar.setBackground(mToolbarBackgroundDrawable);
}

@Override
public void onReviewsRequested(Media media) {
if (mReviewsContainer != null) {
mReviewsContainer.setVisibility(View.VISIBLE);
}
ReviewFragment.insertOnTarget(this, R.id.reviews_view_container, media);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
if (findViewById(android.R.id.list) != null) {
onBackPressed();
return true;
}
}
return super.onOptionsItemSelected(item);
}
}


This is my details fragment:

public class MediaDetailFragment extends Fragment implements DetailsMvpView {

public static final String DETAILS_FRAGMENT_NAME = MediaDetailFragment.class.getName();
private static final String RESOURCE = "resource";
private static final String POSTER_SIZE = "w342/";
private static ViewTreeObserver.OnGlobalLayoutListener mListener;

@Inject
DetailPresenter mDetailPresenter;
@Inject
CreditPresenter mCreditPresenter;
@BindView(R.id.media_image_flipper)
ViewFlipper mImageFlipper;
@BindView(R.id.title_textview)
TextView mTitleTextView;
@BindView(R.id.button_share)
ImageButton mShare;
@BindView(R.id.button_trailers)
ImageButton mTrailers;
@BindView(R.id.button_reviews)
ImageButton mReviews;
@BindView(R.id.keyword_recyclerview)
RecyclerView mKeywordRecyclerView;
@BindView(R.id.overview_content_textview)
TextView mOverviewTextView;
@BindView(R.id.main_image_holder)
ViewGroup mImageHolder;
@BindView(R.id.parent_cardview)
CardView mParentCardView;
@BindView(R.id.cast_recyclerview)
CreditRecyclerView mCastRecyclerView;
@BindView(R.id.crew_recyclerview)
CreditRecyclerView mCrewRecyclerView;
@BindView(R.id.empty_view)
TextView mRecyclerviewEmpty;

@BindString(R.string.youtube_base_url)
String mYoutubeUrl;
@BindBool(R.bool.isTablet)
boolean mIsTablet;

private Media mMedia;
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();

public static MediaDetailFragment insertOnTarget(AppCompatActivity activity, int target, Media media) {
Bundle bundle = new Bundle();
bundle.putParcelable(RESOURCE, media);
MediaDetailFragment fragment = (MediaDetailFragment) MediaDetailFragment.instantiate(activity, DETAILS_FRAGMENT_NAME, bundle);
FragmentManager fm = activity.getSupportFragmentManager();
fm.beginTransaction()
.replace(target, fragment, DETAILS_FRAGMENT_NAME)
.commit();
return fragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMedia = getArguments().getParcelable(RESOURCE);
}

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

View rootView = inflater.inflate(R.layout.media_details_fragment, container, false);
((BaseActivity) getActivity()).activityComponent().inject(this);
ButterKnife.bind(this, rootView);

getActivity().getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);

mTitleTextView.setText(mMedia.title());
mOverviewTextView.setText(mMedia.overview());

mKeywordRecyclerView.setAdapter(new KeywordAdapter(getContext()));
mCastRecyclerView.setAdapter(R.layout.item_credit_normal, mRecyclerviewEmpty);
mCrewRecyclerView.setAdapter(R.layout.item_credit_normal, mRecyclerviewEmpty);

return rootView;
}

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

final View view = getView();
if (view == null) return;
mListener = () -> {
view.post(() -> {
if (mIsTablet) {
mImageFlipper.getLayoutParams().height =
ViewUtil.setHeightForAspectRatio(view.getWidth(), ViewUtil.STANDARD);
} else {
mImageFlipper.getLayoutParams().height =
ViewUtil.setHeightForAspectRatio(mDisplayMetrics.widthPixels, ViewUtil.STANDARD);
}
view.getViewTreeObserver().removeOnGlobalLayoutListener(mListener);
});
};
view.getViewTreeObserver().addOnGlobalLayoutListener(mListener);
}

@Override
public void onStart() {
super.onStart();
mShare.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_TEXT, getActivity().getString(R.string.base_youtube_url)
+ POSTER_SIZE + mMedia.posterPath() + "\n\n"
+ mMedia.title() + "\n\n" + mMedia.overview())
.setType("text/plain")));
mTrailers.setOnClickListener(v -> mDetailPresenter.loadMovies(mMedia.id()));
mReviews.setOnClickListener(v -> ((FragmentHandler) getActivity()).onReviewsRequested(mMedia));

mDetailPresenter.attachView(this);
mDetailPresenter.loadImages(mMedia.id());
mDetailPresenter.loadKeywords(mMedia.id());

mCreditPresenter.attachCastView(mCastRecyclerView);
mCreditPresenter.attachCrewView(mCrewRecyclerView);
mCreditPresenter.loadCredits(mMedia.id());
}

@Override
public void onStop() {
mDetailPresenter.detachView();
mCreditPresenter.detachCastView();
mCreditPresenter.detachCrewView();
super.onStop();
}

@Override
public void showImages(String images) {
ImageView imageView = new ImageView(getContext());
mImageFlipper.addView(imageView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
ViewUtil.loadImage(images, imageView, getContext(), true, true);
}

@Override
public void showKeywords(List<String> keywords) {
((KeywordAdapter) mKeywordRecyclerView.getAdapter()).setKeywords(keywords);
}

@Override
public void showVideos(List<Video> videos) {
TrailerDialogFragment.newInstance(videos).show(getFragmentManager(), null);
}

@Override
public void showError() {

}
}


And, in case it can help, this is my presenter:

@ConfigPersistent
public class DetailPresenter extends BasePresenter<DetailsMvpView> {

private final DataManager mDataManager;
private List<Subscription> mSubscriptions = new ArrayList<>();


@Inject
public DetailPresenter(DataManager dataManager) {
mDataManager = dataManager;
}

@Override
public void attachView(DetailsMvpView mvpView) {
super.attachView(mvpView);
}

@Override
public void detachView() {
super.detachView();
if (mSubscriptions != null) {
for (Subscription subscription : mSubscriptions) {
subscription.unsubscribe();
}
}
}

public void loadImages(String id) {
checkViewAttached();
Subscription subscription = mDataManager.getMovieImages(id)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(getMvpView()::showImages);
mSubscriptions.add(subscription);
}

public void loadKeywords(String id) {
checkViewAttached();
Subscription subscription = mDataManager.getMovieKeywords(id)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(getMvpView()::showKeywords);
mSubscriptions.add(subscription);
}

public void loadMovies(String id) {
checkViewAttached();
Subscription subscription = mDataManager.getVideosForMovie(id)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(getMvpView()::showVideos);
mSubscriptions.add(subscription);
}
}

Answer

Maybe there is something wrong in DataManager

You should always implement onError action. Try to log an error:

  public void loadImages(String id) {
    checkViewAttached();
    Subscription subscription = mDataManager.getMovieImages(id)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
            .subscribe(
                 getMvpView()::showImages,
                 { error -> error.printStackTrace() }
            );
    mSubscriptions.add(subscription);
}
Comments