Using global onCLickListener - java

Is it possible to make an onClickListener app-global?
I basically have several fragments that will use the same numpad buttons for input and instead of registering and filtering click events for each button in each fragment I wanted to ask if it was possible to share an onClickListener throughout the entire app.
This is the setting:
public class LoginFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle states) {
this.context = getActivity();
// TODO: Register onClickListener...somehow
context.registerReceiver(receiver, filter); //For the intent listening
view = inflater.inflate(R.layout.layout_login_screen, container, false);
buildUI(null);
return view;
}
(and two different fragments simmilar to this one)
and then the idea was:
public class NumPadListener implements OnClickListener {
#Override
public void onClick(View v) {
System.out.println("Yup...I'm listening?");
// TODO: Do funny intent stuff here
}
}
Is this even possible? And if yes, how? :) If it isn't, do you have any recommendations on how to implement this in the best way? Thanks

Yes, as dymmeh has shown. But the proper way is probably to create your own view component containing all the numpad buttons. You would need to define a layout file, and create a class which would extend some kind of ViewGroup (see compound controls).
This custom component would take care of onClick events of buttons inside it. You could then expose some kind of interface (listener) for activities and fragments to attach to if you need them to react for higher-level events.
Then, you would just include your custom component in any layouts you need instead of copypasting a bunch of buttons and the onClick listener-attaching code.

Related

Android drawing app intended for a single View needs to work in multi-view Bottom Navigation Activity app

I'm a completely new Android dev and am rather lost at the moment. I'm following this tutorial as I try to enable drawing to a view in my app which is a "Bottom Navigation Activity" project that features three tabs--or fragments. See screenshot below:
The problem is that the tutorial is intended for an app with a single View and a single standard MainActivity. In this case, the following code is used in the onCreate()method in the project's MainActivity.java class:
public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState):
PaintView paintView = new PaintView(content: this):
setContentView(paintView);
}
}
When I build a single view app, the code works great. No issues. But things can't work in my "Bottom Navigation Activity" project, because the fragments use a ViewModel type that doesn't offer the much needed methods of the View class. My app features 3 fragments, the first of which is named HomeFragment. It is on this fragment that I want all my drawing to take place. The default onCreateView() method for this fragment looks like so:
public class HomeFragment extends Fragment {
private HomeView Model homeViewModel;
public View onCreateView(#NonNull LayoutInflator inflater, ViewGroup container, Bundle savedInstanceState) {
homeViewModel = ViewModelProviders.of(fragment: this).get(HomeViewModel.class);
View root = inflater.inflate(R.layout.fragment_home, container, attachToRoot: false);
return root;
}
}
See screenshot below to see what I've tried to do. You will see that I've added a View class to my project called PaintView (just as created in the tutorial), which contains all the drawing code.
Unfortunately it generates the following compilation error:
Inferred type com.example.mobile_testapp_android_2_ui.home.PaintView for type parameter T is not within its bound; should extend androidx.lifecycle.ViewModel
Any tips on how I can implement the tutorial's PaintView class so that I can use its methods to draw on the "HomeFragment" would be deeply appreciated.
Thank you and very cordially,
Wulf
What you are doing is trying to create a HomeViewModel object with the PaintView class which is not possible. If you want to set PaintView as the view of your HomeFragment, you need to return an object of it as :
//you can return any kind of view object as you like
public View onCreateView(#NonNull LayoutInflator inflater, ViewGroup container, Bundle savedInstanceState) {
PaintView homeFragmentView = new PaintView(requireContext());
// PaintView class must extends View class
return homeFragmentView;
}
I hope, this helps.
Learn more about Fragment here
Learn more about ViewModel here

Android: Is there a way to hide MainActivity layout elements in fragments?

My MainActivity layout includes a bottom navigation bar because I would like to use this as the main navigation components within my application's fragments. However, I have a login page which I do not want the navigation bar to be visible on. Since the fragment is being created from the MainActivity, it inherits the navigation bar and I can't find a way to hide it on the login fragment and show it on subsequent fragments.
For ease:
I have a MainActivity layout with a BottomNavigationBar
All fragments natively inherit the BottomNavigationBar from the MainActivity layout
I want the visibility of the navigation bar to be "GONE" on the user login fragment
I want the visibility of the navigation bar to be "VISIBLE" on the remaining fragments
Is there a way to do this?
The reason I am defining the BottomNavigationBar within the MainActivity is that I initially had a separate navigation bar on each fragment which required separate listeners on each fragment (making the code more extensive than it should be). Additionally, when I implemented the navigation bar as an individual component within each fragment, the item selected animations no longer functioned.
I have tried to use <include layout="#layout/main_activity android:visibility="GONE" android:layout_width="match_parent" android:layout_height="wrap_content"/> from within the login fragment but this has not worked.
I would appreciate any help people have on this matter.
Please feel free to let me know if you would like to see any of my code. I wasn't really sure which parts of my code would be relevant.
I would suggest you to use separate activities for this case - LoginActivity and MainActivity. Since you may at some point add ForgotPasswordFragment and maybe something else. To hide something you don't need in the first place is bad practice IMHO.
In case you don't want to change your approach you can create BaseFragment that will have abstract val showBottomNavBar() and will override it in every fragment.
And then in onViewCreated you'll check that flag and update UI accordingly.
Something like this:
abstract class BaseFragment : Fragment() {
abstract val showBottomNavBar: Boolean
//.. your other stuff
}
class FragmentA : BaseFragment() {
override val showBottomNavBar = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (!showBottomNavBar) {
(requireActivity() as YourAcitivy).hideNavBar() //this is bad
someKindOfBroadcastManager.sendEvent(hideNavBar) // this is somewhat better
}
}
}
But I would still suggest you to decouple login/main logic from single activity
You can access activity from fragment with getActivity() method and cast to your activity. Of course you have to add hideBottomNavigationBar method to your activity and also you have to be sure that your fragments host activity is MyActivity.
MyActivity activity = ((MyActivity)getActivity());
activity.hideBottomNavigationBar();
After that you can define a marker interface that named IFullScreen then in your BaseFragment's onCreateView method you can check if this class is instance of IFullScreen and decide to show or hide bottom navigation bar.
interface IFullScreen { }
class BaseFragment extends Fragment {
#CallSuper
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
MyActivity activity = ((MyActivity)getActivity());
if (this instanceof IFullScreen)
activity.hideBottomNavigationBar();
else
activity.showBottomNavigationBar();
}
}
class FragmentA extends BaseFragment implements IFullScreen {
//Rest of fragment code
}

Sending information from one fragment to another Android

I am having this issue where I have nested fragments. I need one to be able to send information to the other.
For example, if I press a button in the inner fragment I want the outer fragment to perform an action.
Any help would be great!
Edit: I forgot to mention that both of these are live fragments. As far as I know using bundles only allows me to set arguments when creating a fragment but not after it has been created.
Well there are plenty of ways to manage this. It really depends on the architecture you want to go with.
You could go lazy and use a Bus library that uses sub/pub modeling and reflection for notifying generic objects that registered and have matching signature methods of changes, However, this is frowned upon to use reflection for regular coding practices that could be implemented without it.
I would say you can either create an interface that represents the calls that would go back and forth like IMainActivityCallbacks with methods like dataChange(myObject obj)
.
Then in your onAttach of fragment you cast the context as IMainActivityCallbacks. Make sure the MainActivity implements the interface.
Then simply call the mIMainActivityCallbacks.dataChange(someObject);
This will call the method in the parent activity and allow him to pass it to the child fragment that needs it with mMyOtherFragment.publicMethod(newDataJustReceived).
You could also get crazier if you want and pass an interface into the child fragment that references the outer fragment so when MainActivity news up the children fragment, the outer one could implement an interface of doStuff and then be passed as an interface to the inner child fragment. So the child fragment can easily say if "mParentInterface" is not null then mParentInterface.doStuff.
There are millions of ways to skin a cat, but prefer to use interfaces rather then Reflection based libraries typically. Hope that helps.
Use interface in this case. 1) Define interface in you first fragment(in which you click something and waiting something to happen in a second fragment) and use callback method you define in your interface. For example:
OnButtonClickListener mCallback;
......your code
public interface OnButtonClickListener {
void onButtonSelected(int position);
}
......your code
then you call your callback method
.....
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
mCallback.onButtonSelected(position);
}
});
2) Then implement this interface in host activity you have for nested fragments.
For example:
public class MainActivity extends AppCompatActivity implements YourFragment.OnButtonClickListener{....
3) Define callback method of your interface in this activity. For example:
public void onButtonSelected(int position) {
...Here just change something in your second fragment.....
Hope this will be helpful.
I use greenrobot EventBus in that cases.
You need to add this library to your build gradle:
compile 'org.greenrobot:eventbus:3.0.0'
That you can send message like this:
EventBus.getDefault().post(new ActionEvent("test action"));
And another fragment can catch it this way:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onActionEvent(ActionEvent event) {
//do something
}
You can sending different actions, if you want, same way.
Don`t forget to according to fragment lifecycle:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}

Is it better to register floating contextual menu click listener in Adapter or in ViewHolder? [ANDROID]

I'm planning to create a floating contextual menu for my app in a fragment after onLongClick and I also have a custom Adapter extending from RecyclerView.Adapter and a custom holder extending from RecyclerView.ViewHolder.
I found out there are 2 options to register :
Option 1 : Registering the listener inside onCreateViewHolder method by holder.itemView.setOnLongClickListener and retrieving any ViewHolder instance variable I want by calling holder.getSomeVariable() inside the event handler and set it to current instance variable so I can retrieve it from Activity/Fragment (because I can get reference of adapter from my fragment).
The advantage I notice is that it allows me to declare the implementation onCreateContextMenu() and onContextItemSelected() method and override them in my Fragment/Activity, which allows the reusing of the existing provided method and get the ViewHolder variables inside the onContextItemSelected().
I notice the downside is that we need to create extra setter and getter method in the adapter (because we cannot reference ViewHolder instance from Activity/Fragment).
Option 2 : Registering the click listener inside ViewHolder. According to this website ,the advantage is it allows more explicit click listener (I also have other listeners registered in the ViewHolder class, so they're group together which makes the code structure looks better).
However, I notice the downside from using this way is we cannot declare onCreateContextMenu() and onContextItemSelected() inside Activity/Fragment anymore because we cannot get the viewholder reference from Activity/Fragment, we can only get adapter reference. As a result, the readability is a bit worse. (I have a contextual_menu.xml, so will need to do the inflation in the viewholder as well instead of in fragment/activity. Is inflation of XML in viewholder even a correct practice?)
From what I understand, in general scenario , it's better to register listener in ViewHolder (Option 2).
But in my context, which method (Option 1 or Option 2) should I apply? Does the general scenario still apply (should I apply Option 2)?
Also are there any more advantages and disadvantages from those 2 listed options?
I use it in Activity or Fragment that way
1- Create interface like that:
public interface OnItemLongClickListener {
void onItemLongClick(int position);
}
EDIT:
2- In your Adapter ViewHolder class create a reference like that:
private OnItemLongClickListener listener;
3- In adapter create setter method to initialize listener which will call another setter method in ViewHolder class like that:
class Adapter extends RecyclerView.Adapter<YourViewHolder>{
.
.
.
.
public void setOnItemLongClickListener(OnItemLongClickListener listener){
viewHolderObject.setonItemLongClickListener(listener);
}
}
public class YourViewHolder extends RecyclerView.ViewHolder{
.
.
.
.
public void setOnItemLongClickListener(OnItemLongClickListener listener){
this.listener = listener;
}
}
4- In your ViewHolder in method onLongClick() do this:
#override
public boolean onLongClick(View v){
listener.onItemLongClick(getAdapterPosition());
}
5- In your Activity Do the contextual menu staff
I hope this help you.!

How to design an Auto-Nested Fragment Class

I'm trying to do something like this:
public class MyFancyFragment<T> extends Fragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
getChildFragmentManager().beginTransaction()
.add(R.id.fancy_fragment_layout_id, new T(), "whatever")
.commit();
...
}
...
}
And later specify to the MyFancyFragment class which inner Fragment it's supposed to be using via, e.g.
...new MyFancyFragment<FancyInnerFragmentType>()...
But of course Java won't let me create a new T because that's not how generics work.
There's a reason I'm trying to do it that way. If you do it the way you'd normally be tempted, i.e.:
public class MyFancyFragment<T> extends Fragment {
...
Fragment toShow;
public void setFragment(Fragment toShow) {
this.toShow = toShow;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
getChildFragmentManager().beginTransaction()
.add(R.id.fancy_fragment_layout_id, toShow, "whatever")
.commit();
...
}
...
}
You'll be fine at first, but the issue comes down to the Android Fragment architecture. If you use MyFancyFragment on a fragment backstack, android could at any time destroy the Fragment, and then go to recreate it later via its public empty constructor. When it does this, it's not going to know to call setFragment() for you. I could go the ViewPager design route and set an Adapter to generate the Fragment on demand; but I'm now actually really confused about ViewPager - if the ViewPager fragment is destroyed and recreated via backstack operations, won't it forget about its Adapter?
I also thought maybe I could take a Fragment type when I create MyFancyFragment the first time (so pass a Class instead of a Fragment to setFragment() above), create an instance using it on the fly in onCreateView(), and try to marshal that class type to my bundle in onSaveInstanceState() for later if I need to onCreateView() again: but I don't know how to store non-primitive types like that in the Bundle? I could setup a primitive<->type instance map, but then I lose compiler support to ensure somebody updates that map when they go to create a new instance; and you're screwed if that new instance you wanted to use would have been an anonymous class.
It seems like the only way to go about doing this is to declare MyFancyFragment abstract and create anonymous inner classes extending it that return the target inner fragment (similar to many common examples involving DialogFragment and its onCreateView()); but this gets really hairy if the level of nesting gets very deep :(. In my present situation I have a DialogFragment that contains an A that contains a B that contains a MyFancyFragment, and if I start requiring dynamic subclassing, I'd have to subclass each of DialogFragment, A, B, and MyFrancyFragment to make it work, for each popup I want to show (and there are several). With generics I thought I could just declare an A() and be ok, and T would trickle down; but I can't figure out how to do it or anything like it.
How are we supposed to do this?

Categories