How do I call a fragment from inside a custom view - android

DogActivity is using a custom View. The custom view handles some logic and so has fields. When a particular field reaches a certain value, I want to start a fragment whose parent is DogActivity. How would I do that?
Is it advisable to put a callback inside a custom view so that it calls its parent activity? Or is there a simpler way?

When programming you should always look for consistency, i.e. look around you and see how similar stuff to what you want to do is done. The Android SDK makes heavy use of callback listeners, so they are the way to go here.
In fact we don't even need to know what kind of View your CustomView really is, we can build a general purpose solution. Don't forget to adapt/optimize according to your specific surroundings however. And think about abstraction and generalisation once you get to a point where all your Views are spammed with listeners!
You will need 3 things:
A listener interface
public interface OnCountReachedListener {
public void onCountReached();
}
A place to accept the listener and a place to alert the listener in your CustomView
public class CustomView extends View {
private int theCount;
private OnCountReachedListener mListener;
public void setOnCountReachedListener(OnCountReachedListener listener) {
mListener = listener;
}
private void doSomething() {
while (theCount < 100) {
theCount++;
}
// The count is where we want it, alert the listener!
if (mListener != null) {
mListener.onCountReached();
}
}
An implementation of the interface in your Activity
public class DogActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
View myView = new CustomView();
myView.setOnCountReachedListener(new OnCountReachedListener() {
#Override
public void onCountReached() {
Log.w("tag", "COUNT REACHED!");
// START YOUR FRAGMENT TRANSACTION HERE
}
});
}
}
For further information look at the source code of the View class and all the On**XY**Listener interfaces in the Android SDK. They should give you plenty to think about

What is the type of the field? Is it an EditText? SeekBar? Depending on the View, you'll be able to specify different listeners/callbacks to determine when they have changed and if they've reached a certain threshold. I would attach these listeners within onCreate of DogActivity. When the threshold is reached, use a FragmentTransaction to add your Fragment as the child of a container View in DogActivity.

Related

How to ccess items inside RecyclerView from parent fragment?

I'm trying to attach an onClickListener() method to an item which is inside a Recycler view. I know I can easily achive that by doing it from the RecyclerAdapter, but the goal of doing that is to show a custom dialog with some information that parent fragment contains, there are some ways to pass data, but I think that's better to attach the listener from fragment instead, and this way I can directly access the data.
I've tried to access from the fragment the way I use to do it from the adapter, with some modifications:
myRecyclerAdapter.myViewHolder.reportContainer.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getContext(),"Touch",Toast.LENGTH_SHORT).show();
}
});
But aparently the myViewHolder object it's not created yet by the time I try to use it, so I get the Java NullPointerException (F..$&##^$&^%, don't misunderstand me, I love it).
So, I need some help to do what I'm trying to, or some other good ideas to try, warning: I;m really trying to avoid passing data, except with maybe a ViewModel (don't know if I can), becouse it's a lot of fields to pass
This is fundamentally incorrect. The problem here is, there are multiple ViewHolders in the RecylerView. Which one do you want to attach it to? There would be n number of items and not all items will be rendered at the same time.
Instead of updating the ViewHolder, use a callback.
class MyAdapter extends RecyclerView.Adapter {
MyAdapterCallback callback = null;
....
#Override
public void onBindViewHolder(holder: ViewHolder, position: Int) {
holder.reportContainer.setOnClickListener { // You can set this in OnCreateViewHolder as well.
if (callback != null) {
callback.onClick();
}
}
}
}
interface MyAdapterCallback {
void onClick()
}
From your fragment,
myAdapter.callback = new MyAdapterCallback() {
#Override
public void onClick() {
// Access your fragment variables here.
}
}

BottomSheetDialog/BottomSheetDialogFragment — which to use and how?

I'm working on a Material design app. One feature I want to implement is some kind of a poll. When a user clicks an element of a list, the persistent bottom sheet dialog, which looks like this should show up:
Then, when user clicks any button, this dialog should go away and the modal bottom sheet dialog should show up, providing a user with more information about the list item which was clicked at the beginning. It looks like this:
I can't find any clear explanations about BottomSheetDialog and BottomSheetDialogFragment, and how to use them correctly, even after reading some information about AppCompat dialogs. So, my questions are:
In what way are they different and which one should I use for each
case?
How to get data in the activity about which button was pressed in the dialog?
Any links to the code of implementations or tutorials about using them?
Finally, I've found the solution and it works. Tell me if I'm doing something wrong. It basically works like DialogFragment from this guide, but I've done it a bit different.
1) Their difference is the same as it of DialogFragment and Dialog, and they both are modal. If you need persistent dialog, use BottomSheetBehaviour instead (I found out that both dialogs had to be modal in my app).
2) I have to answer the third question with some code first, and then it will be easy to answer the second one.
3) Create a new public class, which extends BottomSheetDialogFragment, I called it FragmentRandomEventPoll. There are two two things which have to be implemented here.
Override method onCreateView. It is nearly the same as onCreate method in Activities, except for that it returns the View it should inflate:
// We will need it later
private static String headerItem;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_random_event_poll, container, false);
header = (TextView) v.findViewById(R.id.uRnd_fragment_bottom_sheet_poll_header);
skip = (Button) v.findViewById(R.id.uRnd_fragment_bottom_sheet_button_skip);
header.setText(...);
// I implemented View.OnClickListener interface in my class
skip.setOnClickListener(this);
return v;
}
Static method which you can pass necessary data to and get new instance of this class (Probably I could have just used a regular constructor, I'll have to experiment with it a bit more). URandomEventListItem is the data model class.
public static FragmentRandomEventPoll newInstance(URandomEventListItem item) {
FragmentRandomEventPoll fragment = new FragmentRandomEventPoll();
headerItem = item.getHeader();
return fragment;
}
2) To get input events in activity or any other place, define an interface with necessary methods and create setter method for it's instance:
private PollButtonClickListener listener;
public void setListener(PollButtonClickListener listener) {
this.listener = listener;
}
public interface PollButtonClickListener {
void onAnyButtonClick(Object data)
}
And in the place you want to get your data ("dialog_event_poll" tag was specified in the layout):
FragmentRandomEventPoll poll = FragmentRandomEventPoll.newInstance(events.get(id));
poll.setListener(new FragmentRandomEventPoll.PollButtonClickListener() {
#Override
public void onAnyButtonClick(Object data) {
// Do what you want with your data
}
});
poll.show(getSupportFragmentManager(), "dialog_event_poll");
}
If there is anything unclear, my project files could be found on Github.
About handling events from DialogFragment/BottomSheetDialogFragment.
For applications with many activities, this method is great:
context as MyDialogFragmentListener
But I have a problem with an application with single activity and multiple fragments. Since there can be a lot of fragments, it seems like a very bad option to transfer all events to the necessary fragments through the main activity. Therefore, I decided to do this:
private inline fun <reified T> findListeners(): ArrayList<T> {
val listeners = ArrayList<T>()
context?.let {
if (it is T) listeners.add(it)
if (it is AppCompatActivity) {
it.supportFragmentManager.fragments.forEach { fragment ->
if (fragment is T) listeners.add(fragment)
fragment.childFragmentManager.fragments.forEach { childFragment ->
if (childFragment is T) listeners.add(childFragment)
}
}
}
}
return listeners
}
Code in DialogFragment:
private val listeners by lazy { findListeners<MyDialogFragmentListener>() }
Of course, fragments can contain as many other fragments as you like and probably need to be checked through recursion, but in my case this is superfluous.

Delivering updated list elements via an otto event to Mortar's screen

I'm trying to use Mortar for a simple application that shows a list of items based on mortar-sample.
I'm using Retrofit/Gson to fetch meta-data of these items and Otto to deliver updates to the view once the items are downloaded. I'm also using Dagger for objects creation.
I've an ItemList class that implements ListView to disply the items in a list, and an ItemListScreen that implements mortar.Blueprint.
I wonder where is the best place to put the subscribing method that will update the adapter items?
Currently, I'm placing it under ItemListScreen.Presenter but the subscribe method is never called!!
#Layout(R.layout.item_list_view)
public class ItemListScreen implements Blueprint {
...
#Singleton
public static class Presenter extends ViewPresenter<ItemListView> {
...
#Subscribe
public void onEvent(Event event){
ItemListView view = getView();
if(view == null) return;
view.showItems(event.items);
}
}
}
You don't have your presenter hopping on the bus.
bus.register(this);
Probably want to put that in the onLoad method and then unregister when the view is no longer being displayed.

Sending changing Data from View to Fragment

I have a Activity and its Layout contains a CustomView in which I'm able to draw and I also got a Fragment in which I'll make a statistic about the used colors, stroke widths and one other variable. So I have to send the information to the Fragment.
How can I do that?
My first idea would be to make lists with the colors, strokes..., then adding every drawing to the color list it is made of, then getting the size of the list and at last sending it to the fragment.
Or I could make a variable and every time a color, stroke width and variable is added, the specific variable would increase, but here's the same problem that I don't know how to send the value to the Fragment.
The second problem is that this variable is changing, so if you made one small line in red and you go to the statisitc it should say 1 small red line. If you make another one, then it should say 2 small red lines, how can I receive the latest value of these values?
You can create Interface to communicate between View and Fragment. Read more about Java interfaces here
public class drawView extends View {
private OnColorChangedListener mListener;
//
public interface OnColorChangedListener {
public void colorChanged(int color);
}
public void setOnColorChangedListener(OnColorChangedListener listener){
mListener = listener;
//Call this method to implement this interface in your Fragment
}
#Override
public void onDraw(Canvas canvas){
if(mListener != null){
mListener.colorChanged(somecolor);
}
}
}
public class SomeFragment extends Fragment implements OnColorChangedListener {
public void onCreate(){
drawView v = new drawView();
v.setOnColorChangedListener(this);
}
//If your class claims to implement an interface, all methods defined by that interface must appear in its source code before the class will successfully compile.
public void colorChanged(int color){
//Do your stuff with your color
}
}
One potential solution is to use the Observer/Observable pattern. In this case, your CustomView is the Observable while your Fragment is the Observer. Create a Observer (or Listener interface) with an appropriate name, change the Fragment to implement this interface, and add a method to the CustomView to register the Fragment as a Listener. Now all you need to do is call the onXxx() method from your Listener interface in order to notify the Fragment that a line was drawn. The Fragment is then responsible for calculating the statistics you need.

Unable to setContentView with my tabhost with nested activities

My code below is based on this: http://web.archive.org/web/20100816175634/http://blog.henriklarsentoft.com/2010/07/android-tabactivity-nested-activities/
My issue is with the history ArrayList that is supposed to store the activities so I can properly utilize the back button, however my app crashes when I hit back. I think it's because history isn't exactly storing Views.
I added:
View view = getLocalActivityManager().startActivity("ViewPagerActivity", new
Intent(this,ViewPagerActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
.getDecorView();
to my back method to see if it would load it and it works.
public class FeaturedTabGroup extends ActivityGroup {
// Keep this in a static variable to make it accessible for all the nesten activities, lets them manipulate the view
public static FeaturedTabGroup group;
// Need to keep track of the history if you want the back-button to work properly, don't use this if your activities requires a lot of memory.
private ArrayList history;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.history = new ArrayList();
group = this;
// Start the root activity withing the group and get its view
View view = getLocalActivityManager().startActivity("ViewPagerActivity", new
Intent(this,ViewPagerActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
.getDecorView();
// Replace the view of this ActivityGroup
replaceView(view);
}
public void replaceView(View v) {
// Adds the old one to history
history.add(v);
// Changes this Groups View to the new View.
setContentView(v);
System.out.println("view set successful");
}
public void back() {
if(history.size() > 0) {
history.remove(history.size()-1);
setContentView((Integer) history.get(history.size()-1));
}else {
finish();
}
}
#Override
public void onBackPressed() {
FeaturedTabGroup.group.back();
return;
}
}
EDIT:
For brevity, I'll approach this problem with another question: why does setContentView(v) work, but not when the Views are store in an ArrayList? What happens to the view when it is stored in an arraylist?
Can't you work around this issue with ViewSwitcher (http://developer.android.com/reference/android/widget/ViewSwitcher.html)? It provides a cleaner solution and you could always call showPrevious() and showNext() when needed.
setContentView(int) expects a layout resource ID. In your example, can you try to save the view object itself (i.e. without type cast to integer)? As jcXavier says - viewswitcher is a better way to handle this.
Maybe it's late , but here: (history.size() > 0), change 0 to 1, when size is 1, it should just finish.
Adam,
I know it must be late... but perhaps I can help somebody else.
Here is how I solved the problem:
public void back() {
View v;
if(history.size() > 1) {
history.remove(history.size()-1);
v = (View) history.get(history.size()-1);
FirstGroup.group.setContentView(v);
}else {
finish();
}
}

Categories

Resources