[UPDATE]
Problem solved : Simply add "addToBackStack(null)" before commit fragment :
Fragment fragment = new WebBrowserFragment();
fragment.setArguments(arguments);
FragmentTransaction fragmentTransaction = getActivity().getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Here is the code of my fragment. When i am on this fragment and i click on back button, my application is closed.
I would like to go back on the previous loaded fragment.
What can i do to force this behavior ?
public class WebBrowserFragment extends Fragment {
WebView mWebView;
ProgressBar progressB = null;
private String mUrl;
private static String mtitle;
private static String msource;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LayoutInflater mInflater = (LayoutInflater) getActivity().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = (View) mInflater.inflate(R.layout.web_browser_view, null);
MainActivity.setShareButtonToVisible();
Bundle bundle = getArguments();
String url = bundle.getString("URL");
mtitle = bundle.getString("TITRE");
msource = bundle.getString("SOURCE");
mUrl = url;
progressB = (ProgressBar) view.findViewById(R.id.progressBar1);
mWebView = (WebView) view.findViewById(R.id.webViewArticle);
mWebView.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
if(progress < 100 && progressB.getVisibility() == ProgressBar.GONE){
progressB.setVisibility(ProgressBar.VISIBLE);
}
progressB.setProgress(progress);
if(progress == 100) {
progressB.setVisibility(ProgressBar.GONE);
}
}
});
mWebView.loadUrl(url);
return view;
}
You have to use FragmentTransaction.addToBackStack method when attaching your fragment to Activity. Here is the quote from documentation
Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.
Related
UPDATE: This is my current code, however it breaks after I re-click the same button....
Cause: java.lang.IllegalStateException: Fragment already active
I have a main_activity.xml split into Frame layouts.
The upper frame layout contains an edit text and 3 buttons (small, medium, large).
The lower frame layout contains a text view to populate the text of the edit text in either small, medium or large font.
I put the text into a bundle and am attempting to reopen that bundle in the corresponding fragment once the button is clicked.... however, the bundle does not contain any text information from the text according to my debugger.
Here is my code with the medium, large buttons removed
Any help, please?
MainActivity:
public class MainActivity extends AppCompatActivity {
TextView textView;
Button bSmall;
Small fSmall = new Small();
Bundle bundle = new Bundle();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(findViewById(R.id.fragment)!=null){
if(savedInstanceState!=null){
return;
}
}
final EditText editText = (EditText) findViewById(R.id.type_here);
final Button bSmall = (Button)findViewById(R.id.small);
Button bMedium = (Button)findViewById(R.id.medium);
bSmall.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message_small = editText.getText().toString();
small = message_small;
bundle.putString("message_small", message_small);
fSmall.setArguments(bundle);
FragmentManager fm =getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment, fSmall).commit();
}
});
bMedium.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message_medium = editText.getText().toString();
bundle.putString("message_medium", message_medium);
fMedium.setArguments(bundle);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment, fMedium).commit();
}
});
}
public String getMyData(){
return small;
}
}
Small:
public class Small extends Fragment {
EditText editText;
View myView;
public Small() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
myView = inflater.inflate(R.layout.fragment_small, container, false);
Bundle bundle = new Bundle();
bundle = getArguments();
MainActivity activity = (MainActivity)getActivity();
//String dataFromMainActivity = activity.getMyData();
String myString = bundle.getString("message_small");
TextView set = myView.findViewById(R.id.small_text);
set.setText(myString);
// Inflate the layout for this fragment
return myView;
}
}
You forgot to add the bundle to the fragment.
You need to do something like this:
Small fSmall = new Small();
...
bundle.putString("message_small", message_small);
fSmall.setArguments(bundle);
FragmentManager fm =getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment, fSmall);
I think you forgot to add your bundle to your Fragment before calling the fragment manager.
You should try something like this:
Small fSmall = new Small();
Bundle bundle = new Bundle();
bundle.putString("message_small", message_small); //parameters are (key, value).
fSmall.setArguments(bundle);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment, fSmall).commit();
In your second fragment, you should check if myString is not null or empty.
String myString = getArguments().getString("message_small");
if (myString == null) {
Log.e("TAG", "Error: null argument");
}
EDIT I see another problem here. You are accessing varaibles that have not been instatiated. You should inflate your layout before calling findViewById() or it will return a NullPointerException.
Update your Small class like this :
public class Small extends Fragment {
private EditText editText;
View myView;
public Small() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
myView = inflater.inflate(R.layout.fragment_small, container, false);
String myString = getArguments().getString("message_small");
// Here, myView is != null
TextView editText = myView.findViewById(R.id.small_text);
// Here, editText is != null
editText.setText(myString);
return myView;
}
}
Best
so my code is supposed to support both phone and tablet (tablet with fragments). I know how to inflate the fragment and set the necessary values in the MessageFragment class but I am supposed to do this in the MessageDetails class instead. I don't know how to do that.
ChatWindow class has my code that launches based on if I am on the phone or tablet:
myDisplay.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if(isTablet){
MessageFragment mFragment = new MessageFragment();
Bundle bundle = new Bundle();
String idString = String.valueOf(id);
bundle.putString("message_id", idString);
String message = cursor.getString(cursor.getColumnIndex(ChatDatabaseHelper.KEY_MESSAGE));
bundle.putString("message_value", message);
mFragment.setArguments(bundle);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.frme, mFragment).addToBackStack(null) .commit();
} else { Intent intent = new Intent(ChatWindow.this, MessageDetails.class);
String idString = String.valueOf(id);
String message = cursor.getString(cursor.getColumnIndex(ChatDatabaseHelper.KEY_MESSAGE));
intent.putExtra("message_id", idString);
intent.putExtra("message_value", message);
startActivity(intent); } //for phone
}
});
Here is my onCreateView code for the MessageFragment Class:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup parent, #Nullable Bundle savedInstanceState) {
// Inflate the xml file for the fragment
View rootView = inflater.inflate(R.layout.activity_message_details, parent, false);
return rootView;
}
And Finally, this is my MessageDetails class where I am supposed to inflate the fragment for tablet and assign values (you can see the code for phone layout is already there).
public class MessageDetails extends Activity {
String id;
String message;
MessageFragment mfragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message_details);
Bundle bundle;
TextView delMsg;
TextView delId;
Button delBtn;
bundle = getIntent().getExtras();
setId(bundle.getString("message_id"));
setMessage(bundle.getString("message_value"));
//Missing Fragment Inflater code
delMsg = (TextView) findViewById(R.id.delMsg);
delId = (TextView) findViewById(R.id.delId);
delBtn = (Button) findViewById(R.id.delBtn);
delMsg.setText(message);
delId.setText(id);
delBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(MessageDetails.this, ChatWindow.class);
intent.putExtra ("delete_id", id);
setResult(RESULT_OK);
startActivityForResult(intent, 33);
finish();
}
}
);
}
public void setId(String delId) {
delId = id;
}
public void setMessage(String delMessage) {
delMessage = message;
}
public String getId() {
return id;
}
public String getMessage() {
return message;
}
}
If you want to embed the fragment based on some business logic, then you can try
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
or, If the fragment is already present in your activity's layout xml file, the fragment will be instantiated whenever the activity's layout is inflated by the system and the onCreateView method of the fragment will be called.
As per the official documentation,
When the system creates this activity layout, it instantiates each fragment specified in the layout and calls the onCreateView() method for each one, to retrieve each fragment's layout. The system inserts the View returned by the fragment directly in place of the element.
Please follow the official documentation from the below link for better understanding the Fragment lifecycle.
https://developer.android.com/guide/components/fragments.html#Creating
Since MessageDetails is an Activity. You can try something like this:
getLayoutInflater().inflate(your_xml_layout)
In my program, there are many fragments.
In the ActionBar, I have a SearchView, and result of the search is displayed in fragment0.
When I go to fragment2 (that does not have Webview), I must go back fragment0 to show results.
However, when I go back to fragment0 and call the Webview, it is null.
What's problem and solution?
Here is MainActivity (where search query is excuted):
Fragment fragment0 = new SearchScrActivity();
FragmentTransaction ft = getSupportFragmentManager()
.beginTransaction();
ft.replace(R.id.content_frame, fragment0);
ft.commit();
WebView _webview = (WebView) findViewById(R.id.webViewResult);
Here is SearchScrActivity.java
public class SearchScrActivity extends SherlockFragment {
private WebView _webView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activity_search_screen, container,
false);
_webView = (WebView) rootView.findViewById(R.id.webViewResult);
_webView.setBackgroundColor(0x00000000);
_webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
String meaning;
meaning = "";
_webView.loadDataWithBaseURL(null, meaning, "text/html",
"utf-8", null);
return rootView;
}
}
I have a fragment (MyListFragment) which is a Fragment, i have a list there called myList
this list gets filled with json data onCreateView().
myList -> onItemClick -> open details fragment
after i press the back button the myList list gets reloaded again, i don't want that to happen, instead i want the first fragment to retain the initial state with the loaded data, i used the SlidingPaneLayout for both List/Details fragments, but now i want to use the fragmenttransaction instead cuz i need the SlidingPaneLayout for the menu usage, instead of the list|details slide.
here's a bit of my script
public class MyListFragment extends Fragment {
private AsyncCallBack callback;
public static RelativeLayout loadingListLayout;
public static RelativeLayout listCarsLayout;
public DetailsFragment detailsFragment;
public ArrayList<String> loadedCars = new ArrayList<String>();
public static String loadedCarsString = "";
public static Boolean jsonCarsLoading = false;
public String latestDate;
public static long selectedItem = -1;
public static Boolean isRefreshing = false;
public PullToRefreshListView myList;
public CarsAdapter carsAdapter;
public ArrayList<CarsItems> carsItems;
public ArrayList<CarsItems> results = new ArrayList<CarsItems>();
public static int ON_LOAD_ITEM_COUNT = 20;
public static int ON_REFRESH_ITEM_COUNT = 10;
public static int ON_LOADMORE_ITEM_COUNT = 3;
int currentFirstVisibleItem, currentVisibleItemCount, currentTotalItemCount, currentScrollState;
private View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
view = inflater.inflate(R.layout.list_fragment, container, false);
loadingListLayout = (RelativeLayout) view.findViewById(R.id.LoadingListLayout);
listCarsLayout = (RelativeLayout) view.findViewById(R.id.ListLayout);
myList = (PullToRefreshListView) view.findViewById(R.id.CarsList);
detailsFragment = new DetailsFragment();
carsAdapter = new CarsAdapter(getActivity(), results);
myList.setAdapter(carsAdapter);
...
// Get Cars List if online
if (isOnline()) {
Api jsonCars = new Api();
jsonCars.setLimit(ON_LOAD_ITEM_COUNT);
jsonCars.setCallBack(callback);
jsonCars.getLatestItemDate = true;
jsonCars.setExcludeCars(loadedCarsString);
jsonCars.execute("latestCars");
}
i tried to implement the code inside the onAttach(), but whatever i type in there i guet onLaunchActivity error.....
I solved this issue with the help of savedInstanceState()
private Calendar startTime = Calendar.getInstance();
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("starttime", startTime);
}
i used to load the dataList inside onCreateView, without any condition, so anyway, when i put this code, and checked back the fragment after pressing the Back button, it seemed that the fragment saved it's instance state, like the position and data, but it kept loading the data (Running the AsyncTask again and again);
so i had to put some conditions:
First
i added this line at the top of my fragment class
public Boolean initialized = false;
to be updated later after first run with 'true';
Second
then added those to the onCreateView() before loading the data
if (initialized == false) {
loadList();
} else {
loadingListLayout.setVisibility(View.GONE);
listLayout.setVisibility(1);
}
the loadingListLayout is just an overlay with a progress bar whilst the data is being loaded
Third
and then inside the loadlist() i added :
public void loadList() {
initialized = true;
...
ran the AsyncTask ...
this way i ensured that the data AsyncTask doesn't get executed everytime i press the backbutton, this way i managed to keep using the the addToBackStack for my fragmentTransaction thing.
i solved it this way, adding those methods to my fragment
public int dismiss() {
try {
if (mBackStackId != -1) {
FragmentManager fm = getFragmentManager();
fm.popBackStack(mBackStackId, FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
FragmentTransaction ft = fm.beginTransaction();
ft.remove(this);
ft.commit();
}
} catch (IllegalArgumentException e) {
return mBackStackId;
}
return mBackStackId;
}
public int show(FragmentManager fm) {
FragmentTransaction ft = fm.beginTransaction();
ft.add(android.R.id.content, this, TAG);
ft.addToBackStack(null);
mBackStackId = ft.commit();
return mBackStackId;
}
The Android 4.1 ActionBar provides a useful navigation mode as a list or tab. I am using a SpinnerAdapter to select from three fragments to be displayed in view android.R.id.content.
The onNavigationItemSelected() listener then inflates each fragment to the view and adds it to the back stack using FragmentTransaction.addToBackStack(null).
This all works as advertised, but I don't know how to update the ActionBar to reflect the current back stack. Using the ActionBar.setSelectedNavigationItem(position) works but also triggers a new OnNavigationListener() which also creates another FragmentTransaction (not the effect I want). The code is shown below for clarification. Any help with a solution is appreciated.
public class CalcActivity extends Activity {
private String[] mTag = {"calc", "timer", "usage"};
private ActionBar actionBar;
/** An array of strings to populate dropdown list */
String[] actions = new String[] {
"Calculator",
"Timer",
"Usage"
};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
// may not have room for Title in actionbar
actionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setListNavigationCallbacks(
// Specify a SpinnerAdapter to populate the dropdown list.
new ArrayAdapter<String>(
actionBar.getThemedContext(),
android.R.layout.simple_list_item_1,
android.R.id.text1,
actions),
// Provide a listener to be called when an item is selected.
new NavigationListener()
);
}
public class NavigationListener implements ActionBar.OnNavigationListener {
private Fragment mFragment;
private int firstTime = 0;
public boolean onNavigationItemSelected(int itemPos, long itemId) {
mFragment = getFragmentManager().findFragmentByTag(mTag[itemPos]);
if (mFragment == null) {
switch (itemPos) {
case 0:
mFragment = new CalcFragment();
break;
case 1:
mFragment = new TimerFragment();
break;
case 2:
mFragment = new UsageFragment();
break;
default:
return false;
}
mFragment.setRetainInstance(true);
}
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (firstTime == 0) {
firstTime++;
ft.add(android.R.id.content, mFragment, mTag[itemPos]);
} else {
ft.replace(android.R.id.content, mFragment, mTag[itemPos]);
ft.addToBackStack(null);
}
ft.commit();
Toast.makeText(getBaseContext(), "You selected : " +
actions[itemPos], Toast.LENGTH_SHORT).show();
return true;
}
}
public static class CalcFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_calc, container, false);
return v;
}
}
public static class TimerFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_timer, container, false);
return v;
}
}
public static class UsageFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_usage, container, false);
return v;
}
}
You could do something like this:
Create a boolean to track when you are selecting a navigation item based on the back button:
private boolean mListIsNavigatingBack = false;
Override onBackPressed, in the override, check if the backstack has items, if so handle yourself, if not call the superclass:
public void onBackPressed() {
if(getFragmentManager().getBackStackEntryCount() > 0){
mListIsNavigatingBack = true;
//You need to get the previous index in the backstack through some means
//possibly by storing it in a stack
int previousNavigationItem = ???;
getActionBar().setSelectedNavigationItem(previousNavigationItem);
}
else{
super.onBackPressed();
}
}
Inside NavigationListener, handle the mListIsNavigatingBack state, manually pop the back stack and unset the state:
if(mListIsNavigatingBack){
if(fm.getBackStackEntryCount() > 0){
fm.popBackStack();
}
mListIsNavigatingBack = false;
}