I make the application "Phone book".
The application has a menu, which is sorting by name or category. I do it like this in MainActivity:
Bundle bundle = new Bundle();
Fragment lvFragSortBy = new ListViewFragment();
switch (item.getItemId()) {
case R.id.menuSortOrderName:
item.setChecked(true);
bundle.putString("sortBy", "name");
lvFragSortBy.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.fragContainer, lvFragSortBy)
.commit();
return true;
case R.id.menuSortOrderCategory:
item.setChecked(true);
bundle.putString("sortBy", "category");
lvFragSortBy.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.fragContainer, lvFragSortBy)
.commit();
return true;
In ListViewFragment:
Bundle sortBundle = getArguments();
if (sortBundle != null) {
orderBy = sortBundle.getString("sortBy");
}
And then:
protected ArrayList<Contact> doInBackground(Void... params) {
try {
db = new DbWorker(ctx);
contacts = (ArrayList<Contact>) db.selectAllContacts(orderBy);
} catch (Exception e) {
Log.e(TAG, "Exception in AsyncTask.doInBackground");
}
return contacts;
}
This code works fine but I would like to know can it be done better?
Maybe there's another way.
Thanks to everybody, but I found the answer here: https://codereview.stackexchange.com/questions/147878/the-sorting-in-the-app-with-fragments/147881#147881
For proper operation of the fragments, you must use newInstance() pattern.
Related
So I am getting two errors for my project I am creating the first one is
'Cannot resolve method 'replace(int,UserListFragment,String)'
and
'findFragmentByTag(java.lang.String)' in 'androidx.fragment.app.FragmentManager' cannot be applied to '(int)'
I'm not fully understanding why I am getting these two errors could someone tell me how to fix it and help me understand this error more please.
Here is my Java code:
private void clearMessage(){
mMessage.setText("");
}
private void inflateUserListFragment(){
hideSoftKeyboard();
UserListFragment fragment = UserListFragment.newInstance();
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(getString(R.string.intent_user_list), mUserList);
fragment.setArguments(bundle);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_up);
// This is where my first error is
transaction.replace(R.id.user_list_container, fragment, getString(R.string.fragment_user_list));
transaction.addToBackStack(getString(R.string.fragment_user_list));
transaction.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case android.R.id.home:{
UserListFragment fragment =
// This is where my second error is
(UserListFragment) getSupportFragmentManager().findFragmentByTag(R.string.fragment_user_list);
// (UserListFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.fragment_user_list));
if(fragment != null){
if(fragment.isVisible()){
getSupportFragmentManager().popBackStack();
return true;
}
}
finish();
return true;
}
case R.id.action_chatroom_user_list:{
inflateUserListFragment();
return true;
}
case R.id.action_chatroom_leave:{
leaveChatroom();
return true;
}
default:{
return super.onOptionsItemSelected(item);
}
}
}
I am looking for a way to have an "Are you sure you have finished?" alert box to come up when the user presses the back navigation button whilst on the edit note and add note fragment but not whilst they're on the view note fragment. Below I have attached code from the activity which each fragment is linked to. If you need more of my code from different areas, please let me know.
public class NoteDetailActivity extends AppCompatActivity {
public static final String NEW_NOTE_EXTRA = "New Note";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_detail);
createAndFragment();
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
private void createAndFragment(){
Intent intent = getIntent();
MainActivity.FragmentToLaunch fragmentToLaunch = (MainActivity.FragmentToLaunch) intent.getSerializableExtra(MainActivity.NOTE_FRAGMENT_TO_LOAD_EXTRA);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
switch(fragmentToLaunch){
case EDIT:
NoteEditFragment noteEditFragment = new NoteEditFragment();
setTitle(R.string.edit_fragment_title);
fragmentTransaction.add(R.id.note_container, noteEditFragment, "NOTE_EDIT_FRAGMENT");
break;
case VIEW:
NoteViewFragment noteViewFragment = new NoteViewFragment();
setTitle(R.string.view_fragment_title);
fragmentTransaction.add(R.id.note_container, noteViewFragment, "NOTE_VIEW_FRAGMENT");
break;
case CREATE:
NoteEditFragment noteCreateFragment = new NoteEditFragment();
setTitle(R.string.create_fragment_title);
Bundle bundle = new Bundle();
bundle.putBoolean(NEW_NOTE_EXTRA, true);
noteCreateFragment.setArguments(bundle);
fragmentTransaction.add(R.id.note_container, noteCreateFragment, "NOTE_CREATE_FRAGMENT");
break;
}
fragmentTransaction.commit();
}
#Override
public void onBackPressed() {
new AlertDialog.Builder(this)
.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
NoteDetailActivity.this.finish();
}
})
.setNegativeButton("No", null)
.show();
}
}
Do this by getting the fragment tag
get tag like this
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
String tag = fragment.getTag(); // This will return the tag of fragment
Then use if-else to do the task
if (tag.equalsIgnoreCase("Home")) {
// Open aLERT box
}else if (tag.equalsIgnoreCase("Home2")) {
// Open aLERT box
}else if (tag.equalsIgnoreCase("Home2")) {
// Open aLERT box
}else{
// dO STUFF HERE
}
Don't forget to add the tag of fragment
First of all, try refraining from taking static tags as the names of fragment. Instead,you can set fragment's name as ABCFragment.class.getSimpleName().
Now for the checks on dialogs, create a method
/**
* This method is used to get the top fragmnet on the stack
*
* #return {#link Fragment}
*/
protected final Fragment getTopFragmentStack() {
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = null;
for (int entry = 0; entry < fm.getBackStackEntryCount(); entry++) {
fragment = fm.findFragmentByTag(fm.getBackStackEntryAt(entry)
.getName());
}
return fragment;
}
After this to check which fragment is visible at the moment, you can use the following code
Fragment topRunningFragment = getTopFragmentStack();
if (topRunningFragment instanceof ABCFragment) {
showDialog();
} else if (topRunningFragment instanceof ABCDFragment) {
showDialog();
} else if (topRunningFragment instanceof ABCDEFragment) {
// Do nothing
}
I want two fragments to communicate with each other. ProductDisplayFragment will "send" the object to PaymentFragment. The following code shows the code where I used FragmentTransaction to send object over to PaymentFragment.
This code works except when I try to send over the FIRST object. First object is never received. When I try to add "paymentFragment.getProduct(product);" in else statement, the app will crash.
Here is my code:
Under ProductDisplayFragment.java:
#Override
public void onProductSelected(Product product) {
PaymentFragment paymentFragment = (PaymentFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_payment_list);
if(paymentFragment != null) {
paymentFragment.getProduct(product);
} else {
paymentFragment = new PaymentFragment();
Bundle args = new Bundle();
args.putSerializable(KEY_PRODUCT, product);
paymentFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_payment_list, paymentFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
Under ProductFragment.java
public void getProduct(Product product) {
// Do something to product item
}
Turns out that for the first product I just have to use getArguments for Bundle in onCreateView method as shown below:
// Get first product
Bundle arguments = getArguments();
if (arguments != null) {
Product product = (Product) arguments.getSerializable(KEY_PRODUCT);
getProduct(product);
}
I am pretty new to Android (3 days), but I have a pretty good background in PHP (which may be the cause of my confusion in a Java based environment). I started building an Android app using Android Studio (Beta). I created the default Android Studio activity with the Navigation Drawer Activity. I edited the activity fragment part to look like this:
#Override
public void onNavigationDrawerItemSelected(int position) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("contacts", arr);
bundle.putStringArrayList("messages", messages);
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FriendsFragment();
break;
case 1:
fragment = new ChatsFragment();
break;
case 2:
fragment = new GroupsFragment();
break;
case 3:
fragment = new LogoutFragment();
break;
default:
break;
}
if (fragment != null) {
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, fragment).addToBackStack(null).commit();
}
}
As you can see I am passing a Bundle to my Fragments called "messages" and "contacts" when an item is selected in the Navigation Drawer. The "messages" bundle are XMPP messages received by the aSmack library from an OpenFire server. So basically I'm trying to create a XMPP client. When I run the app I can receive the messages in the "ChatsFragment".
Now my problem:
I have to press the "ChatsFragment" item on the drawer to have my messages updated (re-receive the bundle) everytime I feel like there are new messages received from the server. But I want this to be done automatically by the fragment.
First I would like to know if my procedure is correct (Activity listens to server, creates bundle, send bundle to fragment, bundle updates messages on receive**)
** = This part I haven't been able to understand how to implement.
1- If the procedure is correct tell me how I should get the messages to be updated by the fragment through the activity?
2- If this is not the correct way to do things in Android, recommend me a way of doing it.
My code for displaying the messages in fragment:
private void displayListView() {
// Messages array list
List<String> contacts = getArguments().getStringArrayList("messages");
//System.out.println("arr: " + contacts);
//create an ArrayAdaptar from the String Array
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(getActivity(),
R.layout.url_list, contacts);
ListView listView = (ListView) getView().findViewById(R.id.listView);
// Assign adapter to ListView
listView.setAdapter(dataAdapter);
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// Send the URL to the host activity
//mListener.onURLSelected(((TextView) view).getText().toString());
}
});
}
Thanks in advance.
Typically for long running operations in the background, like listening to a server, incoming messages, etc, you need to use Services. You do so by subclassing the Service class in Android.
Now for your problem - the design approach should be that you have a background service listening to incoming messages. Anytime a message is received (an input stream in your socket operator) you should send a "broadcast" an intent that a message was received. A custom broadcast receiver that you create should wait for this broadcast. Within the onReceive() method of this receiver, you should trigger the creation of the bundle and updating your message.
Remember you should always delegate your long running operations in Android to services. That is exactly what they are for.
So basically if you're already listening for new messages to come in your activity, then you must have some kind of callback like onMessageRecieved() or something like that.
If you do, you can then notify your fragment in this way.
Create a field (goes under your class declaration) called curFrag, so something like this:
public class MyActivity extends Activity {
private Fragment curFrag;
//other code...
}
then in the code you posted you would initialize the curFrag there, but you also need to set a tag for the current fragment. This will be based on your case statement. Make some final string variables as tags.
i.e.
public static final String CHATSTAG = "chatstag";
#Override
public void onNavigationDrawerItemSelected(int position) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("contacts", arr);
bundle.putStringArrayList("messages", messages);
Fragment fragment = null;
String tag = null;
switch (position) {
case 0:
tag = FRIENDSTAG;
fragment = new FriendsFragment();
break;
case 1:
tag = CHATSTAG;... and so on through the switch statement.
fragment = new ChatsFragment();
break;
case 2:
fragment = new GroupsFragment();
break;
case 3:
fragment = new LogoutFragment();
break;
default:
break;
}
if (fragment != null) {
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
//remember to set the tag.
if(tag != null) {
fragmentManager.beginTransaction()
.replace(R.id.container, fragment, tag).addToBackStack(null).commit();
} else {
fragmentManager.beginTransaction()
.replace(R.id.container,fragment,DEFAULTTAG).addToBackStack(null).commit();
}
//new code
curFrag = fragment;
}
}
Now in your activity when a new message comes in, check the tag of the fragment and then if it matches a certain fragment, notify the fragment that new data has come in and retrieve it in the fragment.
public void onMessageRecieved() {
if(curFrag.getTag().equalsIgnoreCase(CHATSTAG)) {
ChatsFragment frag = (ChatsFragment) curFrag;
frag.notifyDataRecieved();
}
}
Once you have a reference to your fragment in the activity, you have access to any public methods in that fragment.
If your fragment cannot access the data on its own, then you'll need to get a reference to the activity and create a method in the activity that returns the new data.
So in your activity:
public String getMessageData() {
String newData = ...//get stuff from server;
return newData;
}
then in your fragment
public void notifyNewMessage() {
try {
MyActivity activity = (MyActivity) getActivity();
} catch (ClassCastException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
String message = activity.getMessageData();
//do something with the message.
}
It's not necessarily pretty but it works pretty well. You should also check to make sure your fragments are attached when you do this so that you avoid null exceptions.
Hope this helps!
In my main layout I have a central container which contains a fragment.
I started with FragmentTransaction.replace(...) to replace the fragment with other fragments but this recreate each fragment on each replacement, which makes the UI laggy (some fragments have complexe layout etc).
Instead of replace(), some suggest using .show() and .hide() to "cache" rendered fragments, and which leads to the code below:
private HashMap<String, Fragment> mCachedFragments = new HashMap<String, Fragment>();
private Fragment mCurrentFragment = null;
#Override
public void onNavigationDrawerItemSelected(int position) {
if (position == mCurrentSectionID)
return;
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
if (mCurrentFragment != null) {
Log.d("TRANSACTION", "putting fragment "+mCurrentFragment.getClass().getName());
mCachedFragments.put(mCurrentFragment.getClass().getName(), mCurrentFragment);
transaction.hide(mCurrentFragment);
}
Log.d("Nav", "selected: " + position);
Fragment dest = null;
switch (position) {
case BitsListFragment.FRAGMENT_ID:
dest = mCachedFragments.get(BitsListFragment.class.getName());
if (dest == null) {
dest = BitsListFragment.newInstance();
transaction
.add(R.id.container, dest);
} else {
Log.d("TRANSACTION", "bitlist retrieved");
transaction.show(dest);
}
onSectionAttached(BitsListFragment.FRAGMENT_ID);
break;
case AchievementsFragment.FRAGMENT_ID:
dest = mCachedFragments.get(AchievementsFragment.class.getName());
if (dest == null) {
dest = AchievementsFragment.newInstance();
transaction
.add(R.id.container, dest);
} else {
Log.d("TRANSACTION", "achievements retrieved");
transaction.show(dest);
}
onSectionAttached(AchievementsFragment.FRAGMENT_ID);
break;
case StatsFragment.FRAGMENT_ID:
dest = mCachedFragments.get(StatsFragment.class.getName());
if (dest == null) {
dest = StatsFragment.newInstance();
transaction
.add(R.id.container, dest);
} else {
Log.d("TRANSACTION", "stats retrieved");
transaction.show(dest);
}
onSectionAttached(StatsFragment.FRAGMENT_ID);
break;
}
mCurrentFragment = dest;
transaction.commit();
}
The code here works perfectly as long as the Activity does not gets recreated. The fragments gets retrieved and shown, and currentFragment is hidden when transition takes place.
As you can see, I'm basically keeping a reference to the CurrentFragment and a hashmap of all the cached fragments. The problem is that when I rotation the screen, the MainActivity gets recreated and these references are reset to null or emptied. Which ends up with BitsListFragment getting created multiple times and shown on screen overlapped.
Is there a better way to cache fragments/replace fragments without recreating them?
Thanks!
Actually found another way to do this: instead of keeping an reference on my own, we can add the fragment to the backstack and then retrieve it using corresponding tag. This let's fragmentManager manages the caching by itself. And the second time you access a fragment, it doesn't gets recreated.
The only hack that you should watch out is to override onBackPressed() so that you don't return to last fragment if you don't need to.
#Override
public void onNavigationDrawerItemSelected(int position) {
if (position == mCurrentSectionID)
return;
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
Fragment dest;
switch (position) {
case BitsListFragment.FRAGMENT_ID:
dest = fragmentManager.findFragmentByTag(BitsListFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = BitsListFragment.newInstance();
}
transaction.replace(R.id.container, dest, BitsListFragment.class.getName());
transaction.addToBackStack(BitsListFragment.class.getName());
break;
case AchievementsFragment.FRAGMENT_ID:
dest = fragmentManager.findFragmentByTag(AchievementsFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = AchievementsFragment.newInstance();
}
transaction.replace(R.id.container, dest, AchievementsFragment.class.getName());
transaction.addToBackStack(AchievementsFragment.class.getName());
break;
case StatsFragment.FRAGMENT_ID:
dest = fragmentManager.findFragmentByTag(StatsFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = StatsFragment.newInstance();
}
transaction.replace(R.id.container, dest, StatsFragment.class.getName());
transaction.addToBackStack(StatsFragment.class.getName());
break;
}
transaction.commit();
onSectionAttached(position);
}