2

Note: See update 2 for what I currently have


Currently in my fragment's onCreateView(), I have an adapter for a recyclerview initialized like this:

Query query = couchLocalDb.getDatabase().getView(MY_VIEW_NAME).createQuery();
LiveQuery liveQuery = query.toLiveQuery();

if (myAdapter == null) {
    myAdapter = new MyAdapter(getActivity().getApplication(), new ArrayList<>(), getActivity(), liveQuery);
}

And this is the constructor of the said adapter:

public MyAdapter(Application application,
                 List<MyViewHolder> myViewHolderList,
                 Context context,
                 LiveQuery liveQuery) {
    this.myViewHolderList = myViewHolderList;
    this.context = context;
    this.liveQuery = liveQuery
    LiveQuery.ChangeListener listener = new LiveQuery.ChangeListener() {
        @Override
        public void changed(LiveQuery.ChangeEvent event) {
            ((Activity)MyAdapter.this.context).runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    enumerator = event.getRows();
                    notifyDataSetChanged();
                }
            });
        }
    };
    ((MainApplication)application).getAppComponent().inject(this);
    this.liveQuery.addChangeListener(listener);
    this.liveQuery.start();
}

But recently I'm trying to learn how to use Dagger 2 for dependency injection, and I think I shouldn't have "new" anywhere besides in the Dagger's AppModule?

So how would I write the provide method in my AppModule class for this when I need the context of the fragment? Or am I doing this injection completely wrong and totally missed the idea?

The context variable is only ever used in this line:

((Activity)MyAdapter.this.context).runOnUiThread(new Runnable()...

Here's what I have so far in my AppModule class:

@Provides
MyAdapter provideMyAdapter() {
    return new MyAdapter(mainApplication, new ArrayList<>(), idk_context, getLiveQuery);
}

private LiveQuery getLiveQuery() {
    return couchLocalDb.getDatabase().getView(MY_VIEW_NAME).createQuery().toLiveQuery();
}

I'm not sure how I'm supposed to find a way to use the getActivity() to get the context from my fragment.

Any hint?


Update: Is it okay to do this?

@Module
public class AppModule {
    private MyFragment myFragment = new MyFragment(); // So I just initialize this here instead
    private MainApplication mainApplication;

    public AppModule(MainApplication mainApplication) {
        this.mainApplication = mainApplication;
    }

    @Provides
    MyAdapter provideMyAdapter() {
        return new MyAdapter(mainApplication, new ArrayList<>(), myFragment.getActivity(), getLiveQuery());
    }

    @Singleton
    @Provides
    MyFragment provideMyFragment() {
        return myFragment;
    }

    private LiveQuery getLiveQuery() {
        return couchLocalDb.getDatabase().getView(MY_VIEW_NAME).createQuery().toLiveQuery();
    }
}

Update 2: I ended up doing this: I added a thing to just provide activity and call it a day. Is this okay to do?

@Module
public class AppModule {
    private MainApplication mainApplication;

    public AppModule(MainApplication mainApplication) {
        this.mainApplication = mainApplication;
    }

    @Provides
    @Singleton
    MyAdapter provideMyAdapter(Activity activity, LiveQuery liveQuery) {
        return new MyAdapter(mainApplication, new ArrayList<>(), activity, liveQuery);
    }

    @Provides
    @Singleton
    MyFragment provideMyFragment() {
        return new MyFragment();
    }

    // I added this method and just let this provide the activity to my provideAdapter method.
    @Provides
    @Singleton
    Activity provideActivity(MyFragment myFragment) {
        return myFragment.getActivity();
    }

    @Provides
    @Singleton
    LiveQuery provideLiveQuery() {
        return couchLocalDb.getDatabase().getView(MY_VIEW_NAME).createQuery().toLiveQuery();
    }
}

Here's the new constructor

public MyAdapter(Application application,
                 List<MyViewHolder> myViewHolderList,
                 Activity activity,
                 LiveQuery liveQuery) {
    this.myViewHolderList = myViewHolderList;
    this.liveQuery = liveQuery
    LiveQuery.ChangeListener listener = new LiveQuery.ChangeListener() {
        @Override
        public void changed(LiveQuery.ChangeEvent event) {
            (activity).runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    enumerator = event.getRows();
                    notifyDataSetChanged();
                }
            });
        }
    };
    ((MainApplication)application).getAppComponent().inject(this);
    this.liveQuery.addChangeListener(listener);
    this.liveQuery.start();
}

Is this a okay thing to do? Not a bad practice or anything?

Juliette_Evans
  • 232
  • 2
  • 13
  • 1
    Is `MainApplication` an Application class? Then that should be a Context. Making a new Fragment, will not yet have any Activity to get – OneCricketeer Nov 11 '16 at 02:46
  • Yes it is an Application class. – Juliette_Evans Nov 11 '16 at 02:54
  • 1
    Okay. That class extends Context, so why get the Activity from the Fragment? – OneCricketeer Nov 11 '16 at 02:57
  • 1
    As far as I can tell, this seems to be a similar post. http://stackoverflow.com/questions/30692501/dagger-2-injecting-android-context – OneCricketeer Nov 11 '16 at 03:00
  • I think that I thought that I need to getActivity() from fragment so I can use it for ((Activity)MyAdapter.this.context).runOnUiThread because I thought the UI thread was different for some reason... So you're saying I don't have to do that? I can just use mainApplication instead? – Juliette_Evans Nov 11 '16 at 03:04
  • 1
    I don't really know what you're trying to run on the UI thread. Seems like you are using Couchbase Lite, and I thought that used RxJava bindings for async code – OneCricketeer Nov 11 '16 at 03:07
  • @cricket_007 Yes I'm indeed using couchbase lite, and I'm still learning how to use everything correctly; the examples I looked up all use runOnUiThread to handle the liveQuery update so that's what I used. Is there a better way to do this? In my run method, I update the enumerator which is used to fill in the information for each row in my recyclerview, then I notifyDataSetChanged. So that has to be run on the ui thread because it calls the onBindViewHolder and updates the ui, right? – Juliette_Evans Nov 11 '16 at 03:12
  • 1
    Right, though, I wouldn't put a thread in an Adapter. I've been meaning to learn couchbase lite myself, so can't give any tips on that. Anyways, your Adapter just needs a Context, not an application. If you are having issues with updating the adapter on the UI thread, then move the query to the Activity containing the adapter – OneCricketeer Nov 11 '16 at 03:24
  • 1
    For example, the Grocery sync example puts the query outside the adapter. https://github.com/couchbaselabs/GrocerySync-Android/blob/master/GrocerySync-Android/src/main/java/com/couchbase/grocerysync/MainActivity.java – OneCricketeer Nov 11 '16 at 03:26
  • @cricket_007 Ohhh, so is that's why every examples I ran into all used livequery.addChangeListener instead of this.livequery.addChangeListener...they didn't add "this." because they probably don't want to put the thread into the adapter (I thought every single one of them made the same typo simultaneously) – Juliette_Evans Nov 11 '16 at 03:43
  • Updated update 2 to show what I ends up doing – Juliette_Evans Nov 16 '16 at 16:41

2 Answers2

1

You can use getActivity(), which returns the activity associated with a fragment.
The activity is a context (since Activity extends Context).
Here's an example for Context in Fragment :

//import android.app.Fragment;
import android.support.v4.app.Fragment
public class CalendarFragment extends Fragment
{
    static Context mContext      = null;                   //member variable
    ...

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    mContext = this.getActivity();                         //set mContext
    ((Activity) mContext).startManagingCursor(notesCursor);//cast to Activity
    ...
Jon Goodwin
  • 9,053
  • 5
  • 35
  • 54
  • You really don't need a static Context variable. `getActivity()` can always be used – OneCricketeer Nov 11 '16 at 02:44
  • So basically, I would use MyFragment.mContext in my provideMyAdapter method, and don't inject my adapter until mContext has been initialized? – Juliette_Evans Nov 11 '16 at 02:59
  • 1
    Your provideMyAdapter is ok (context is passed in), you can tied up in knots sometime, this way is simple and efficient. – Jon Goodwin Nov 11 '16 at 03:23
  • I mean using a static context, won't that caused the context to be held on longer than it should? Although, in my specific case my fragment lives as long as the application anyway so it probably won't matter – Juliette_Evans Nov 16 '16 at 16:02
0

Yes, it is almost ok, but I think it will be better if you will create some scopes for your fragments. Because if you have big count of fragments you app will create your fragment before than you need and it will be exists during all time when your app is work

Pein
  • 1,059
  • 3
  • 10
  • 31
  • Sorry, could you clarify a little bit more about the "create some scopes" part? My AppModule class is only initialized once in my MainApplication class, which only execute once. My app has only one main activity and one main fragment and everything happens in that fragment. Is initializing my fragment in AppModule going to cause some unwanted results? – Juliette_Evans Nov 11 '16 at 02:45
  • No, If you have only one fragment, than you will not have any problems. But if count of fragments is so big, than you should not create all fragments when applications starts – Pein Nov 11 '16 at 20:25