Is there a way to call a method after building a fragment? - java

I want to change a Fregment and as soon as the Gui is built a method should be executed.
I have solved it so far with a thread. Only I thought that there would have to be a better way.
OnResume () is executed before the fragment is formed.
Does anyone know a way that without a thread to solve?
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
fragmentRoot = inflater.inflate(R.layout.fragment_l2c_printersettings, container, false);
fragmentRoot.setFocusableInTouchMode(true);
fragmentRoot.requestFocus();
...
...
...
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
loadPrinter();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return fragmentRoot;
}

I think you may be looking for public void onActivityCreated(Bundle savedInstanceState) in your fragment.
Fragment lifecycle.
From the documentation
Called when the fragment's activity has been created and this fragment's view hierarchy instantiated. It can be used to do final initialization once these pieces are in place, such as retrieving views or restoring state. It is also useful for fragments that use setRetainInstance(boolean) to retain their instance, as this callback tells the fragment when it is fully associated with the new activity instance. This is called after onCreateView(LayoutInflater, ViewGroup, Bundle) and before onViewStateRestored(Bundle).

You can call the function in onViewCreated. By the time onViewCreated is called, the fragment is completely loaded.

Related

Handling configuration changes when there is a background thread running - Android

I have a fragment that launches a background thread to do some work and uses a handler callback to update some UI when the thread finishes its task.
public class CodeFragment extends Fragment implements View.OnClickListener, Handler.Callback {
private CodeDataViewModel codeDataViewModel;
private Handler uiHandler;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_code, container, false);
uiHandler = new Handler(Looper.getMainLooper(), this);
return view;
}
#Override
public boolean handleMessage(Message msg){
if(msg.what == 0){
BFResult result = (BFResult)msg.obj;
codeDataViewModel.setOutput(result);
runButton.setIcon(ResourcesCompat.getDrawable(requireActivity().getResources(), R.drawable.ic_baseline_play_arrow_24, null));
runButton.setText(getString(R.string.run));
codeDataViewModel.setShouldRun(true);
if(result.errorCode != BFErrorCodes.UserAbort){
ViewPager2 viewPager2 = getActivity().findViewById(R.id.view_pager);
viewPager2.setCurrentItem(1);
}
}
codeDataViewModel.setInterpreterThread(null);
return true;
}
The issue I have is when the screen rotates while the thread is still running, then, after the thread finishes executing and when handleMessage starts being executed I get the following exception in handleMessage at the line runButton.setIcon(...)
FATAL EXCEPTION: main
java.lang.IllegalStateException: Fragment CodeFragment{7f397b9} (ea45dd9d-5419-4d22-8f56-11c7fed9f3bd) not attached to an activity.
at androidx.fragment.app.Fragment.requireActivity(Fragment.java:928)
at com.sandeept.brainfkinterpreter.fragments.CodeFragment.handleMessage(CodeFragment.java:117)
Now, I know this is because the activity and the fragments are recreated when a configuration change occurs. And because the handleMessage function being executed is of the old fragment that is now no longer attached to the activity there is a run time error.
What I don't know is how to fix this. I thought about using setRetainInstance(true), but this has been deprecated since the introduction of view models. I don't know how view models are going to be helpful in this case.
So, how do I handle this issue?

Android fragment navigation without button click

I am using a "Drawer Navigation" project using fragments and so far this method works:
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.nav_gallery);
}
});
But I want to use the navigation method inside a fragment class calling a function like this:
boolean b;
public void fragmentNavigation() {
if (b) {
Navigation.findNavController(getView()).navigate(R.id.nav_gallery);
}
}
I am a newbie using the navigation architecture and I still don't know if I need to declare some type of action listener for that function or how to make it work.
You can do that, but be careful of :
using getView() is Nullable; so you must make sure that your fragment has already created the view.
You can solve this by a couple of ways
First: Overriding onViewCreated() which has a nonNull view argument
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
fragmentNavigation(view);
}
boolean b;
public void fragmentNavigation(View view) {
if (b) {
Navigation.findNavController(view).navigate(R.id.nav_gallery);
}
}
Second: Create a View field in the fragment class, and set it within onCreateView()
View view;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.main_fragment, container, false);
return view;
}
then always call fragmentNavigation(view); on that view field.
Your fragment is hosted by the NavHostFragment of the Navigation Graph; so that you can avoid potential IllegalStateException

How to use base activity in fragment?

Here i implemented a emoji-keyboard.
For the use of this library , activity extended to EmojiCompatActivity.
prepareKeyboard(EmojiCompatActivity activity, EmojiEditText input
This is working well in activity. While using in fragment i tried this one
prepareKeyboard((ActivityName)getActivity,input)
Here is my MainActivity
public class MainActivity extends EmojiCompatActivity {}
And fregment class is
public class PagerEmojKeyboard extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.emoji_keyboard, container, false);
EmojiEditText userMessageInput = getActivity().findViewById(R.id.input_message);
EmojiKeyboardLayout emojiKeyboardLayout=(EmojiKeyboardLayout) v.findViewById(R.id.keyboard_emoj);
emojiKeyboardLayout.prepareKeyboard((MainActivity) this.getActivity(),userMessageInput);
return v;
}
}
You can refer to the base activity as this.activity.
Sometimes, while using fragments, the above method also works.
If you are working in the method onCreateView you shouldn't have problems but if you have other method there is a View variable that you must have as a global for use throughout the class.

onResume() - for switching between multiple CHILD fragments

The basic problem I have is i'm trying to get a refresh function to execute every time i return to a specific fragment.
So far I've been using the workaround of utilizing setUserVisibleHint() as shown below.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
refresh();
}
}
However, this only works when switching between my main fragment and this fragment in question. What i'm wondering is how do i get this code to execute when i switch from one of my other tabs?
Help would be much appreciated.
Root cause: This is a feature from Android called offscreen page limit, it will retain the number of fragments to either side of current fragment, default value is 1. In your case using setUserVisibleHint is not enough to refresh data.
Solution:
public class YourFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,Bundle savedInstanceState) {
if (getUserVisibleHint()) { // fragment is visible to users.
refresh();
}
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()) { // fragment is visible to users
refresh();
}
}
public void refresh(){
}
}
Fragments inside Pager act as visible even if there are not currently visible to user, the affected fragments are differ based on Pager offscreenPageLimit so trying to refresh fragment inside setUserVisibleHint or onResume has no effect when using Pager.
However that is fine as it is intended behavior, the aim of Pager is to let user switch between pages as you switch tabs in your browser..
If you still insist on refreshing pages you can do this inside the Pager addOnPageChangeListener like this:
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
Fragment fragment = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + position);
if(fragment != null && fragment instanceof RefreshableFragment)
((RefreshableFragment) fragment).refresh();
}
});
Under the fragment, you should call your method under onResume() callback of fragment.
public void onResume(){
super.onResume();
.....
}

Proper way of communication between fragments

Is this a proper way of communication between fragments ?
public class MainActivity extends AppCompatActivity implements IFragmentsHandler {
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
}
#Override
protected void startFragment1() {
Fragment1 f1 = new Fragment1();
f1.setFragmentsHandler(this);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, f1)
.commit();
}
#Override
protected void startFramgment2() {
Fragment1 f2 = new Fragment1();
f2.setFragmentsHandler(this);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, f2)
.commit();
}
}
public class Fragment1 {
private IFragmentsHadnler fragmentsHandler;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment1, container, false);
//...Code...
fragmentsHandler.startFragment1();
}
public void setFragmentsHandler(IFragmentsHandler fragmentsHandler) {
this.fragmentsHandler = fragmentsHandler;
}
}
public class Fragment2 {
private IFragmentsHadnler fragmentsHandler;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment2, container, false);
//...Code...
fragmentsHandler.startFragment2();
}
public void setFragmentsHandler(IFragmentsHandler fragmentsHandler) {
this.fragmentsHandler = fragmentsHandler;
}
}
[EDIT1] : Posted the Interface (though it was obvious)
public interface IFragmentsHandler {
public void startFragment1();
public void startFragment2();
}
From my Java perspective this will throw OutOfMemoryError but I'm not if it is the same for the Android. Anyway what is the preferred way of communication between fragments?
According to android developer guide, communication between fragments is done through the associated Activity.
A fragment use his interface to communicate with the Activity. And the Activity deliver a message by capturing the Fragment instance with findFragmentById() or creating one if needed, then directly call the other fragment's public methods.
Fragment1 wants to pass some data: uses his interface method implemented by Activity.
Activity executes that method receiving the data, create&replace or find (depending on your layout) the Fragment2 and pass this data or execute some public method on fragment2 class (depending on your layout).
Fragment2 extract data from bundle or execute (depending on your layout) his public method to receive the data.
I think the problem in your code is you are misunderstanding interface purpose. You are using for start the same fragment who is calling the method. Fragment1 is calling startFragment1() in his onCreateView(), but it is already started.
If you needed, in here there is a good tutorial.
To communicate between components consider app architecture MVP, VIPER, etc. On code side it may use event bus for communication or just plain callbacks.
Do navigation in one place
Do business logic in another place
Do present-view logic in presenter
Do view logic in views, fragments, adapters
As you started, you can use interfaces to communicate between Fragments as suggested by Google.
But an easy way to communicate between fragments is by using event bus (which implements the publish/subscribe pattern) like EventBus library.
You can also use RxJava to create your own event bus and thus make communications between components of your app (have a look to this Stackoverflow question: RxJava as event bus?)

Categories