I'm creating an android application that contains one main activity and three fragments. There is a side navigation bar and depend on the selected menu item, it will replace the fragment loaded into the main screen.
In one of those fragments, I'm loading data from a REST API and once the data received, it will update the fragment UI. I'm using ok-http to send REST API calls. So in the onResponse() method, I have to call getActivity().runOnUiThread() to update the UI elements with received data and show Toast notifications for errors.
I already read that there is a chance to return null while calling getActivity() method within a fragment due to detach the fragment from the activity. I'm also aware that it's not a good idea to create a reference to the activity in onAttach() method and use it later like below, since it will prevent GC to clean that activity object after detaching from the fragment.
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
mActivity = (Activity)context;
}
Therefore I'm currently doing null check before calling getActivity() method as shown below which is recommended in most of the SO answers.
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
//Update UI
}
);
}
But according to the above code block, It's obvious that if there is no activity attached to the fragment (when getActivity() returns null), the UI will not be updated.
It's okay to not executing the UI updates if the fragment is not on the screen anymore. So my question is, is there any possibility of showing the fragment UI to the user while getActivity() returning null?
I am following the one activity/ many fragment approach for this application. From the picture, you can see there is a main activity with a toolbar spinner with sorting preferences. The option selected by the user will dictate how the recyclerView sort itself.
My question is, what is the best approach for fragment to obtain the information about what the user has selected in the toolbar spinner while keeping separation of concern?
Here is how the activity gets notified of user event in the spinner:
#Override
public void onSortMenuItemClicked(MenuItem id) {
mFragmentScreenNavigator.swapToJobRequestFragment(mUserProfilePersistence.getUserProfile().getEmail());
}
Once the user has selected a sorting preference, I have a navigator that navigates to the recyclerView fragment
Basically, what I want to achieve is instead of 'telling' the fragment what the sorting preference is, is there a way for the fragment to 'go out' and get that information? thank you
You could use ViewModel for the purpose of communication between Activity and Fragment. To make it work you also can use LiveData to observe for variable changes and updating if it happens.
1) Don't forget to add corresponding dependencies.
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
2) Create a class that extends from ViewModel. In this class, we have MutableLiveData object that allows up to work with LiveData. Probably it's better to use Integer type so you could change it like id.
public class ExampleViewModel extends ViewModel {
private MutableLiveData<String> sortingMode = new MutableLiveData<>();
public LiveData<String> getSortingMode() {
return sortingMode;
}
public void setSortingMode(String mode) {
sortingMode.postValue(mode);
}
}
2) Get a reference to your ViewModel inside Activity so you could update the value with sorting mode from (I believe) your options menu
ExampleViewModel viewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
3) To change the value you simply need to call your ViewModel method that will update the value o variable depending on item/id/title (whatever sounds better for you).
viewModel.setSortingMode("Example Mode");
4) Get the Activity ViewModel inside Fragment
ExampleViewModel viewModel = ViewModelProviders.of(getActivity()).get(ExampleViewModel.class);
5) Subscribe to LiveData object to observe value changes
viewModel.getSortingMode().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
// Provide needed logic depending on sorting preferences
}
});
It's just a little example of how it works. Architecture Components are really powerful so I strongly recommend you to read about it.
Result (Green sector is Fragment inside Activity, they both are observing):
link
This question is mostly to solicit opinions on the best way to handle my app. I have three fragments being handled by one activity. Fragment A has one clickable element the photo and Fragment B has 4 clickable elements the buttons. The other fragment just displays details when the photo is clicked. I am using ActionBarSherlock.
The forward and back buttons need to change the photo to the next or previous poses, respectively. I could keep the photo and the buttons in the same fragment, but wanted to keep them separate in case I wanted to rearrange them in a tablet.
I need some advice - should I combine Fragments A and B? If not, I will need to figure out how to implement an interface for 3 clickable items.
I considered using Roboguice, but I am already extending using SherlockFragmentActivity so that's a no go. I saw mention of Otto, but I didn't see good tutorials on how to include in a project. What do you think best design practice should be?
I also need help figuring out how to communicate between a fragment and an activity. I'd like to keep some data "global" in the application, like the pose id. Is there some example code I can see besides the stock android developer's information? That is not all that helpful.
BTW, I'm already storing all the information about each pose in a SQLite database. That's the easy part.
The easiest way to communicate between your activity and fragments is using interfaces. The idea is basically to define an interface inside a given fragment A and let the activity implement that interface.
Once it has implemented that interface, you could do anything you want in the method it overrides.
The other important part of the interface is that you have to call the abstract method from your fragment and remember to cast it to your activity. It should catch a ClassCastException if not done correctly.
There is a good tutorial on Simple Developer Blog on how to do exactly this kind of thing.
I hope this was helpful to you!
The suggested method for communicating between fragments is to use callbacks\listeners that are managed by your main Activity.
I think the code on this page is pretty clear:
http://developer.android.com/training/basics/fragments/communicating.html
You can also reference the IO 2012 Schedule app, which is designed to be a de-facto reference app. It can be found here:
http://code.google.com/p/iosched/
Also, here is a SO question with good info:
How to pass data between fragments
It is implemented by a Callback interface:
First of all, we have to make an interface:
public interface UpdateFrag {
void updatefrag();
}
In the Activity do the following code:
UpdateFrag updatfrag ;
public void updateApi(UpdateFrag listener) {
updatfrag = listener;
}
from the event from where the callback has to fire in the Activity:
updatfrag.updatefrag();
In the Fragment implement the interface in CreateView do the
following code:
((Home)getActivity()).updateApi(new UpdateFrag() {
#Override
public void updatefrag() {
.....your stuff......
}
});
To communicate between an Activity and Fragments, there are several options, but after lots of reading and many experiences, I found out that it could be resumed this way:
Activity wants to communicate with child Fragment => Simply write public methods in your Fragment class, and let the Activity call them
Fragment wants to communicate with the parent Activity => This requires a bit more of work, as the official Android link https://developer.android.com/training/basics/fragments/communicating suggests, it would be a great idea to define an interface that will be implemented by the Activity, and which will establish a contract for any Activity that wants to communicate with that Fragment. For example, if you have FragmentA, which wants to communicate with any activity that includes it, then define the FragmentAInterface which will define what method can the FragmentA call for the activities that decide to use it.
A Fragment wants to communicate with other Fragment => This is the case where you get the most 'complicated' situation. Since you could potentially need to pass data from FragmentA to FragmentB and viceversa, that could lead us to defining 2 interfaces, FragmentAInterface which will be implemented by FragmentB and FragmentAInterface which will be implemented by FragmentA. That will start making things messy. And imagine if you have a few more Fragments on place, and even the parent activity wants to communicate with them. Well, this case is a perfect moment to establish a shared ViewModel for the activity and it's fragments. More info here https://developer.android.com/topic/libraries/architecture/viewmodel . Basically, you need to define a SharedViewModel class, that has all the data you want to share between the activity and the fragments that will be in need of communicating data among them.
The ViewModel case, makes things pretty simpler at the end, since you don't have to add extra logic that makes things dirty in the code and messy. Plus it will allow you to separate the gathering (through calls to an SQLite Database or an API) of data from the Controller (activities and fragments).
I made a annotation library that can do the cast for you. check this out.
https://github.com/zeroarst/callbackfragment/
#CallbackFragment
public class MyFragment extends Fragment {
#Callback
interface FragmentCallback {
void onClickButton(MyFragment fragment);
}
private FragmentCallback mCallback;
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt1
mCallback.onClickButton(this);
break;
case R.id.bt2
// Because we give mandatory = false so this might be null if not implemented by the host.
if (mCallbackNotForce != null)
mCallbackNotForce.onClickButton(this);
break;
}
}
}
It then generates a subclass of your fragment. And just add it to FragmentManager.
public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.add(R.id.lo_fragm_container, MyFragmentCallbackable.create(), "MY_FRAGM")
.commit();
}
Toast mToast;
#Override
public void onClickButton(MyFragment fragment) {
if (mToast != null)
mToast.cancel();
mToast = Toast.makeText(this, "Callback from " + fragment.getTag(), Toast.LENGTH_SHORT);
mToast.show();
}
}
Google Recommended Method
If you take a look at this page you can see that Google suggests you use the ViewModel to share data between Fragment and Activity.
Add this dependency:
implementation "androidx.activity:activity-ktx:$activity_version"
First, define the ViewModel you are going to use to pass data.
class ItemViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Item>()
val selectedItem: LiveData<Item> get() = mutableSelectedItem
fun selectItem(item: Item) {
mutableSelectedItem.value = item
}
}
Second, instantiate the ViewModel inside the Activity.
class MainActivity : AppCompatActivity() {
// Using the viewModels() Kotlin property delegate from the activity-ktx
// artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.selectedItem.observe(this, Observer { item ->
// Perform an action with the latest item data
})
}
}
Third, instantiate the ViewModel inside the Fragment.
class ListFragment : Fragment() {
// Using the activityViewModels() Kotlin property delegate from the
// fragment-ktx artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by activityViewModels()
// Called when the item is clicked
fun onItemClicked(item: Item) {
// Set a new item
viewModel.selectItem(item)
}
}
You can now edit this code creating new observers or settings methods.
There are severals ways to communicate between activities, fragments, services etc. The obvious one is to communicate using interfaces. However, it is not a productive way to communicate. You have to implement the listeners etc.
My suggestion is to use an event bus. Event bus is a publish/subscribe pattern implementation.
You can subscribe to events in your activity and then you can post that events in your fragments etc.
Here on my blog post you can find more detail about this pattern and also an example project to show the usage.
I'm not sure I really understood what you want to do, but the suggested way to communicate between fragments is to use callbacks with the Activity, never directly between fragments. See here http://developer.android.com/training/basics/fragments/communicating.html
You can create declare a public interface with a function declaration in the fragment and implement the interface in the activity. Then you can call the function from the fragment.
I am using Intents to communicate actions back to the main activity. The main activity is listening to these by overriding onNewIntent(Intent intent). The main activity translates these actions to the corresponding fragments for example.
So you can do something like this:
public class MainActivity extends Activity {
public static final String INTENT_ACTION_SHOW_FOO = "show_foo";
public static final String INTENT_ACTION_SHOW_BAR = "show_bar";
#Override
protected void onNewIntent(Intent intent) {
routeIntent(intent);
}
private void routeIntent(Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case INTENT_ACTION_SHOW_FOO:
// for example show the corresponding fragment
loadFragment(FooFragment);
break;
case INTENT_ACTION_SHOW_BAR:
loadFragment(BarFragment);
break;
}
}
}
Then inside any fragment to show the foo fragment:
Intent intent = new Intent(context, MainActivity.class);
intent.setAction(INTENT_ACTION_SHOW_FOO);
// Prevent activity to be re-instantiated if it is already running.
// Instead, the onNewEvent() is triggered
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(intent);
There is the latest techniques to communicate fragment to activity without any interface follow the steps
Step 1- Add the dependency in gradle
implementation 'androidx.fragment:fragment:1.3.0-rc01'
Here is my scenario: In my application i have a Main activity, two fragments and a service that runs in background. I attach the fragments to my main activity using this piece of code:
Fragment f1;
f1=new loginfragment();
FragmentTransaction ft=getFragmentManager().beginTransaction();
ft.add(R.id.frame,f1);
ft.commit();
Which is working fine. one of these fragments contains a listview which is attached to an adapter.(i have verified that adapter is correctly attached to the listview and entries can be added to listview without any problems.
In order to be able to inflate a layout inside the listview adapter(which extends BaseAdapter) i created a constructor to feed Context to it.
background service checks a page periodically and sends the results to one of these fragments using an interface that the fragment implements(sending broadcasts didn't work for me).interface is defined like this:
interface resultInterface{
void receive(String s,Context con);
}
fragment implements the interface like this:
#Override
public void receive(String s,Context con){
String elements[]=s.split("<br>");
if(elements.length>1) {
if(elements[0].equals("REQUEST")) {
init_notification("REQUEST",detect(elements[1],elements[2]));// a function that shows a notification
item i = new item(elements[1], elements[2]);
orders.add(i);
adapter = new listview_adapter(orders,c);
list.setAdapter(adapter);}
}
}
orders is an Arraylist and c is the context that is being passed to the listview adapter. service calls this interface method like this:
resultInterface resultinterface=new servefragment();
resulstinterface.receive(s,this);
upon receiving data (which results in calling the interface method) the fragment method is supposed to split the data and add entries to the listview.(i have verified that data is being correctly passed to the interface method.
BUT, the view that is being shown as listview entries is not correct( look corrupted somehow). Here is how it is supposed to look(using dummy data):Like this but here is how they are actually added(1 item is received from server which is correct but the layout is not): Like this
If you need more information just comment and i'll add them
My guess is that the Context object your service is passing to you has a different theme than you're expecting. This can cause text colors, button colors, etc to all change.
In order to be able to inflate a layout inside the listview adapter(which extends BaseAdapter) i created a constructor to feed Context to it.
Unless you need the Context instance somewhere other than inside getView(), you don't have to pass a Context via a constructor. Here's the full signature of the getView() method:
public View getView(int position, View convertView, ViewGroup parent)
The parent argument is guaranteed to be non-null, and all View instances have a getContext() method, so you can always retrieve a Context by writing parent.getContext(). Additionally, this will be your Activity (as opposed to something like your Application), which has the added benefit of having the correct theme.
So delete all of the code that passes the Context into your adapter and just use parent.getContext() instead, and I bet the problem goes away.
So currently I have a application with different activities, and I use buttons to navigate between activities. I later decided that I should add the navigation drawer and use fragments instead. So one of my fragments has a bunch of fields that the user fills out that I need to pass onto the next fragment. So my question is, Do I keep all the work in activities and call the activity from the fragment? Or do I just include all he java in my activity in the java for the fragment? For the most part I am taking the fields from the first fragment, and I'd like to pass the values to the next fragment so it can handle some calculations.
final EditText FinalPriceText = (EditText) v.findViewById(R.id.editTextPrice);
final EditText TradeInPriceText = (EditText) v.findViewById(R.id.editTextTrade);
i.putExtra("FinalAutoPrice", FinalAutoPriceText.getText().toString());
i.putExtra("TradeInPrice", TradeInPriceText.getText().toString());
startActivity(i);
As far as calculations go, if you are going to use them a lot and they have nothing to do with the activity or android lifecycle I would separate them out into a different class and then you can call them from anywhere.
If they do rely on the activity you could still separate them out but pass a reference to the activity when doing your calculations. You can get the parent activity by calling this.getActivity() from any fragment
You can cast this.getActivity() to whatever the parent activity is and you can call the methods from that object as well. This works fine but your fragment will only work with the activity you specify and it can get sloppy if you are not careful.
Otherwise put them in the fragment where you need them. I would consider this least recommended if you need to use calculations anywhere else in the app. Duplicate code is just asking for bugs in the future.
As far as passing data, create a static instance method in fragment2 and pass it what you need there.
For example
public Fragment2 extends Fragment {
public static fragment2 newInstance(MyData myDataIPass) {
Fragment2 fragment = new Fragment2();
Bundle args = new Bundle();
args.putInt("someInt", myDataIPass.someInt);
fragment.setArguments(args);
return fragment;
}
}
Call this new instance method when creating your fragment transaction like this
FragmentManager fm = getActivity().getFragmentManager();
fm.beginTransaction()
.replace(R.id.container, Fragment2.newInstance(MyData myDataIPass))
.commit();
Well, you have mainly two options here:
use Intent.putExtra() with fragments. Just like Activities, you
can use this method with Fragments as well. See the following links
for the implementation in Fragments
this
this
The other option is to use SharedPreferences and store data as key value pairs from one fragment, and can be accessed from any other activity/fragments. See this nice tutorial to understand better!
You have a special callback in Fragment to get Activity.
It is called:
#Override
public void onAttach(Context context) {
super.onAttach(context);
YourActivity activity = (YourActivity) context;
}