FragmentTransaction.replace() crashes - but only in release build - java

when I execute the following code in debug (ADB via USB) it works, but when I install a generated release .apk, it crashes. More precisely the following code crashes.
.replace(R.id.settings, new SettingsFragment())
Here is the full version of the code.
public class SettingsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
static class SettingsFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
}
}
}
I found the error from the Google Play which says it throws an IllegalStateExcetion. I do not know any other way of retrieving error messages since it works perfectly fine in debug mode.
This is the corresponding XML file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

I believe disabling Instant run may resolve this. Take a look at this thread:
Android App Crashes on Real Device If apk is manually installed

Had to make SettingsFragment public, now it looks like it works.

Related

Class not found when unmarshalling in PreferenceFragment

I try to implement a day/night system changeable directly from the settings. I used PreferenceFragmentCompat for this but however, when I press the switch preference to change the mode. I have the following error:
E/Parcel: Class not found when unmarshalling: androidx.fragment.app.FragmentManagerState
java.lang.ClassNotFoundException: androidx.fragment.app.FragmentManagerState
Caused by: java.lang.ClassNotFoundException: androidx.fragment.app.FragmentManagerState
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
I looked everywhere but I don't really understand where the error comes from..
This is my Preference Activity:
public class PreferencesActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preferences);
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
if (savedInstanceState == null)
getSupportFragmentManager().beginTransaction().replace(R.id.preferences_container, new PreferencesFragment()).commit();
}
#Override
protected void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.contains(getString(R.string.KEY_PREF_NIGHT_MODE)))
AppCompatDelegate.setDefaultNightMode(sharedPreferences.getBoolean(key, false)? AppCompatDelegate.MODE_NIGHT_YES: AppCompatDelegate.MODE_NIGHT_NO);
}
public static class PreferencesFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
}
And I use this layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/noteRecyclerBackground"
tools:context=".PreferencesActivity">
<FrameLayout
android:id="#+id/preferences_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</FrameLayout>
</LinearLayout>
There is no crash but an error and this is when the activity restart with AppCompatDelegate.setDefaultNightMode(sharedPreferences.getBoolean(key, false)? AppCompatDelegate.MODE_NIGHT_YES: AppCompatDelegate.MODE_NIGHT_NO);
Well, per the documentation, this sounds like expected behavior.
If this method is called after any host components with attached AppCompatDelegates have been 'created', a uiMode configuration change will occur in each. This may result in those components being recreated, depending on their manifest configuration.

App crashes when trying to call SetSupportActionBar

I have made an app that works fine and uses a default toolbar. However I want to add navigation buttons to this toolbar so I am implementing my own that I can inflate a menu onto it. However when I try to call SetSupportActionBar() the app crashes.
I have tried to set the app to not use the default action bar in both the manifest: <activity android:name=".MainMenu" android:theme="#style/Theme.AppCompat.NoActionBar"/> as well as in the XML file: android:theme="#style/Theme.AppCompat.NoActionBar", as this was the suggested solution for someone else with a similar issue however this has not worked.
The code I am using is as follows;
XML:
<Toolbar
android:id="#+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Java:
public class MainMenu extends AppCompatActivity {
Toolbar mToolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_menu);
mToolbar = findViewById(R.id.my_toolbar);
setSupportActionBar(mToolbar);
mToolbar.setNavigationIcon(R.drawable.menu_arrow);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
}
}
Change the toolbar's xml to:
<android.support.v7.widget.Toolbar
android:id="#+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
instead of Toolbar
also look at your imports, if you have this line:
import android.widget.Toolbar;
replace it with:
import android.support.v7.widget.Toolbar;

Using Butterknife with BaseActivity/Child Activities and MaterialDrawer

I'm facing issues implementing MaterialDrawer (library MikePenz) using Multiple Activities ( not using fragments) which is implemented with a BaseActivity. Also, using ButterKnife to bind the views.
Found related issues here in SO and tried it out one by one , but in my case it was not working since most of them using standard Navigation Drawer with Fragments or they are not related with ButterKnife.
Code :
MainActivity class
public class MainActivity extends AppCompatActivity {
#BindView(R.id.toolbar)
Toolbar toolbar;
private List<TestModel> destinations;
private DestinationAdapter mAdapter;
ProgressDialog progressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
navManagement();
}
}
activity_main layout
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/maincontainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.tvs.ui.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay"
/>
</android.support.design.widget.AppBarLayout>
</android.support.constraint.ConstraintLayout>
StoreActivity
public class StoresActivity extends MainActivity {
#BindView(R.id.searchEditText)
EditText mSearchEditText; // errorRequired view 'searchEditText' with ID 2131558557 for field 'mSearchEditText' was not found
#BindView(R.id.storesRecyclerView)
RecyclerView recyclerView; // here too
private List<TestModel> stores;
private StoreAdapter mAdapter;
//endregion
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stores);
ButterKnife.bind(this);
loadRecyclerView();
testData();
}
}
activity_store layout
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/searchEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
<android.support.v7.widget.RecyclerView
android:id="#+id/storesRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>
I understand that cause of error is that the ButterKnife.Bind in MainActivity is called when the Layout is inflated but the Views are not inflated in the inherited Activities . The Navigation drawer loads but on clicking item it fails as obvious reason. I cannot use a FrameLayout since I'm using MaterialDrawer library which doesn't ( or I'm not aware) have views to inflate.
I tried several methods extensively searching SO like SO1SO2 Git Git2
Link but was not successful .
Appreciate any help are deeply appreciated . Thanks in advance.
Thanks #mikepenz for the response.
Posting it since it might help somebody
The issue was fixed by creating an abstract method in BaseActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResourceId());
ButterKnife.bind(this);
setSupportActionBar(toolbar);
navManagement();
}
protected abstract int getLayoutResourceId();
Concrete Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_stores); not required
ButterKnife.bind(this);
loadRecyclerView();
testData();
}
#Override
protected int getLayoutResourceId() {
return R.layout.activity_stores;
}
Please note that I was not using ButterKnife.Bind in the Concrete Activity and used #Nullable fields. But currently Binding in both Base and Implementing Activities.
The MaterialDrawer will inflate views to your layout. By default it will add a DrawerLayout into the root of your activity, and add the other content (which is your main layout) as child to it again.
This decision was made to make it super simple to implement a Drawer into your application without needing to alter anything special.
Butterknife will bind the view's at the time you call the bind method.
All views of the MaterialDrawer, are bound at the time of you calling .build(), but as the views of the MaterialDrawer are bound internally you shouldn't have to worry about these.
To complete #user2695433 's answer, you can go a little futher : handle all butterknife init and close in the BaseActivity so you can't forget to release ressources ! Very useful if some new dev come in your team and dont know how butterknife work. Here it is :
--------In BaseActivity-------
protected Unbinder binder;
protected abstract int getLayoutResourceId();
...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResourceId());
binder = ButterKnife.bind(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
binder.unbind(); //release ressource
}
----- In ConcreteActivity------------
#BindView(R.id.title)
TextView title; // butterknife stuff
#Override
protected int getLayoutResourceId() { //define which Id
return R.layout.activity_main;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

Stuck at adding/changing fragments in android studio

even though i googled extensively, i cant find a solution to my problem. I am new to programming, so please consider that in your answer.
I have a Main Activity and a Menu bar at the bottom. Since the menu bar is scrollable and i want to have it for all the different screens, i figured out that i could - instead of making an intent for a new activity - just put a fragment on top of the existing screen (and spare out the menu bar).
But i fail at programatically opening that fragment. I tried the following, but it doesnt even recognice the ID of the FrameLayout.
I was trying to replace a FrameLayout in my Main Activities' xml file with the fragment:
FragmentManager fragmentManager = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft =
fragmentManager.beginTransaction();
ft.replace(R.id.idOfFrameLayout, new nameOfFragmentClass());
ft.commit();
EDIT:
It works after i implemented OnFragmentInteractionListener into the Main Activity. Thanks to everyone!
My original answer suggested using add instead of replace for the Fragment transaction. I have since learned that either can be used in this case. For more info on the differences, see this, this and this
Since my add/replace idea was off base, I've changed my answer to a simple Fragment tutorial. One Fragment inflated from one Activity. I've included the code for Fragment to Activity communication (the reason for the OPs problem) but left comments on what could be deleted if that's not needed. The only thing done in the Activity is launching the Fragment then receiving a message back from that Fragment. The Fragment has a button that will cause a message to be sent back to the Activity. The message will then be Logged.
In both classes, if no communications is needed from the Fragment to the Activity, delete anything that is marked TODO:
// this is what controls whether you use a Fragment from the support library
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class SimpleActivity extends AppCompatActivity
// TODO: delete "implements SimpleFragment.OnFragmentInteractionListener" from this
// line (leave the '{')
implements SimpleFragment.OnFragmentInteractionListener {
private static final String TAG = SimpleActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
SimpleFragment fragment = new SimpleFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
// TODO: this entire method can be deleted
#Override
public void onFragmentInteraction(String message) {
// This is where you can act on the message from the Fragment. You would do
// things that are done from an Activity or you may pass the message on
// to another Fragment.
Log.d(TAG, message);
}
}
Most import statements aren't shown, I left this one in to indicate using the support library
import android.support.v4.app.Fragment;
public class SimpleFragment extends Fragment {
private OnFragmentInteractionListener mListener; // TODO: delete this line
public SimpleFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_simple, container, false);
Button button = (Button)rootView.findViewById(R.id.msg_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// TODO: delete these 3 lines, do something else with the button click
if (mListener != null) {
mListener.onFragmentInteraction("Message from Fragment");
}
}
});
return rootView;
}
// TODO: this entire method can be deleted
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
// TODO: this entire method can be deleted
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
// TODO: this entire method can be deleted
public interface OnFragmentInteractionListener {
void onFragmentInteraction(String message);
}
}
Now the layout files. 1st activity_simple.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container"
>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
Then fragment_simple.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Info to Activity"
android:id="#+id/msg_button"
/>
</RelativeLayout>
This is a very basic sample. Here is a more extensive guide for Fragment usage.
When you use the 'replace' method, you need 3 things:
1- the container id, which is the view that is going to hold your fragment's view.
2- the fragment instance you want to use and
3- the fragment instance's tag, but this is optional.
Given that, let's say you have the following layout for your activity:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
...
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:menu="#menu/navigation" />
</android.support.design.widget.CoordinatorLayout>
The FrameLayout which id is "container" is the holding view, so that's the id you've gotta use:
ft.replace(R.id.container, new nameOfFragmentClass());
I suggest use this library to manage fragments:
FragmentManagerAndroid

Android Fragment is not displayed

I'm quite new to Android dev and I followed the official Android's "Get started".
The fact is, my fragment is not displayed on the main activity ( it worked well few days ago but I changed some lines, I don't remember which ones ). I think it's a very basic problem as I don't use sophisticated fragments : it's basically one fragment inside an activity.
This is my activity :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mysecond.MainActivity"
tools:ignore="MergeRootFrame" />
My fragment :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
</RelativeLayout>
And the java code for this activity (I have some other activities in the app, based on the same pattern "one fragment inside one activity" and they work well...)
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container1, new PlaceholderFragment()).commit();
}
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}
}
Any ideas ?
Thank you :)
[edit]
so this is my new onCreate method :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().replace(
R.id.container1, new PlaceholderFragment()).commit();
}
Still not working for this activity (If I add a button in activity_main.xml I'll be able to see it but the I'm not able to see the TextView in the fragment...)
No errors in logcat and yes the activity is launched (I added some Log.e in onCreate and onCreateView and I cas see them)
In your onCreate methode you don't have to check if the savedInstanceState is null but if the the content of the FrameLayout you use is null
Or you simply always replace the fragment with a new one and ommit any checking.
Instead of add, you can use replace.
You can do it like below shown code:
getSupportFragmentManager().beginTransaction().replace(
R.id.container1, new PlaceholderFragment());
For me this work. Implementa a interface FragmentActions with the init() method and use this
private void showFragment(String fragmentTag){
FragmentTransaction trasaction = getSupportFragmentManager().beginTransaction();
FragmentActions fragment = (FragmentActions) getSupportFragmentManager().findFragmentByTag(fragmentTag);
if(fragment==null ){
if(lastFragmentviewed!=null)
trasaction.hide(lastFragmentviewed);
fragment = (FragmentActions) newInstance(fragmentTag);
trasaction.add(R.id.content_frame,(Fragment) fragment,fragmentTag);
}else{
if(lastFragmentviewed!=null && !lastFragmentviewed.equals(fragment))
trasaction.hide(lastFragmentviewed);
if(getSupportFragmentManager().findFragmentByTag(fragmentTag)!=null){
fragment.init();
trasaction.show((Fragment) fragment);
}else
trasaction.add(R.id.content_frame,(Fragment) fragment,fragmentTag);
}
lastFragmentviewed=(Fragment) fragment;
trasaction.commit();
}

Categories