how to inject custom arrayadapter into fragment using roboguice 2.0 - android

I have a fragment that contains a listView. The fragment extends roboFragment. The listView has a custom adapter that sets up different UI elements in the getView method. I want to use #InjectView to get the ui elements. I understand that in order to do this, I need to also create the adapter using guice and not the new operator. So this is what my fragment does:
#Inject
TweetsActivityAdapter tweetsAdapter;
The adapter looks like this:
public class TweetsActivityAdapter extends ArrayAdapter<ITweet> {
#InjectView(R.id.ivProfilePic)
ImageView ivProfilePic;
#InjectView(R.id.tvUserName)
TextView tvUserName;
#InjectView(R.id.tvTweet)
TextView tvTweet;
private final static String tag =
"Debug - com.codepath.upkar.twitterapp.TweetsActivityAdapter";
#Inject
public TweetsActivityAdapter(Context context, List<ITweet> tweets) {
super(context, 0, tweets);
}
I read that I need to configure guice and tell it where to get ITweet from. ITweet is just the interface for Tweet model class.
public interface ITweet {
public User getUser();
public void setUser(User user);
public String getBody();
public long getId();
public long getStrId();
public boolean isFavorited();
public boolean isRetweeted();
}
How do I create a binding for the List? I am currently getting an error:
FATAL EXCEPTION: main
E/AndroidRuntime(18753): java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.bindaas.twitterapp/com.bindaas.twitterapp.activities
.TwitterAppActivity}: com.google.inject.ConfigurationException:
Guice configuration errors:
E/AndroidRuntime(18753): 1) No implementation for
java.util.List<com.bindaas.twitterapp.models.ITweet> was bound.
My module is as follows:
public class MyCustomModule implements Module {
#Override
public void configure(Binder binder) {
binder.bind(ITweet.class).to(Tweet.class);
}
}

There is 2 main problems with your code sample:
1) You told the injector how to create an ITweet instance, not a List<ITweet>.
Also, how do you expect RoboGuice to know what tweets you want in that list ?
You could implement a Provider to do something like that, but that seems a bit too much IMO. A simpler way would be to add a setter to your adapter, and set the data to your adapter after it has been instantiated by RoboGuice.
2) You cannot use #InjectView in a ArrayAdapter
If you look at the code of RoboActivity, you'll see this code in the onContentChanged() handler:
#Override
public void onContentChanged() {
super.onContentChanged();
RoboGuice.getInjector(this).injectViewMembers(this);
eventManager.fire(new OnContentChangedEvent());
}
The injectViewMembers() method is the one doing the magic behind #InjectView.
Sadly this method only accept an Activity or a Fragment.
You might have a look at Butterknife (by Jake Wharton), which is leaner than RoboGuice for views injection.

Related

Notify Activity from onClick of View object

I seem to be stuck with a problem with an object communicating with my activity class. The object is a view object with an onClick method that when called I would like it to notify my activity class so that it can perform said action. Below is some example code of my situation (assume all conventional setup operations have already been made):
public class MainActivity extends AppCompatActivity{
//...other global methods and objects
//Does not have access to instantiated Entry object(s)
public void entryObjectWasClicked(){
//perform said action
}
}
public class Entry extends View implements View.OnClickListener{
//...other global methods and objects
//Does not have access to the MainActivity object
#Override
public void onClick(View v){
//send a message to the MainActivity to
//somehow call the entryObjectWasClicked() method
}
}
The only way (off the top of my head) that I could think about dealing with this problem is by creating a static method in MainActivity and then calling it from an anonymous MainActivity object in the onClick method of Entry. The problem with the static method approach is that any subsequent method/object/primitive usages in the static method force those methods/objects/primitives to be static. This defeats the purpose of then being able to have two different instances of the MainActivity object.
After some looking I came across using Broadcast messages, specifically using the LocalBroadcastManager to send an intent to the activity. This code example works for my model, but I want to know: is this the best way for me to go about sending messages to my MainActivity from my Entry object?
If there is a more effective way of doing all this, what would it be?
You're overcomplicating things. Don't override onClick for this. Instead, have your activity call setOnClickHandler on your view, which sets a callback that's called when the view is clicked. Then use the default implementation.
Since you extend view, i guess you want to use it inside a layout. That means you may want to create a Listener for that. Example:
public class Entry extends View implements View.OnClickListener{
private OnClickListener listener;
public void setListener(OnClickListener listener) {
this.listener = listener;
}
#Override
public void onClick(){
if (this.listener != null) this.listener.onClick(this);
}
}
How you can inflate your layout in your Activity and access your custom view.
public class MainActivity extends AppCompatActivity{
public void onCreate( ...) {
Entry entry = findViewById(R.id.entry);
entry.setListener(new OnClickListener(...));
}
}

Is it possible to avoid unnecessary injection in parent class?

Given the following example:
class CustomView extends View {
#Inject
SomeObject mObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getApplicationComponent().inject(this);
}
}
class SecondaryCustomView extends CustomView {
#Inject
AnotherObject mAnotherObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getApplicationComponent().inject(this);
}
}
Both custom views may be used on a layout independently. The second is just a bit more specialized than the first one.
As you can see, both have fields to inject and both need to call inject(). The problem is that when SecondaryCustomView calls its inject(), Dagger injects an instance of AnotherObject and an instance of SomeObject. After the call to super.onFinishInflate(), it creates a second instance of SomeObject. This is not a problem per se, but we are at least creating unnecessary objects.
Is there a way to avoid this? Some way to tell Dagger that a child class has been injected, so ignore the parent injection?
As an example, the Component looks like this:
#Component(...)
public interface AppComponent {
void inject(CustomView);
void inject(SecondaryCustomView);
}
There's no way to do this in Dagger, but you can do it yourself.
To agree with and expand on your point in the comments:
yes it is needed. Dagger does not inject the child objects if we use injection on parent only. however it injects the parent objects if it is called from the child.
That's correct, and noted in "A note about covariance": Though inject(Foo) can accept an instance of Foo or any of its subclasses, Dagger's a compile-time framework; inject(Foo) will not be generated to inject the fields belonging to arbitrary subclasses of Foo because, well, that's impossible to know at compile time. This can be a little surprising, particularly if your Component has both inject(Foo) and inject(FooSubclass) as you have for CustomView and SecondaryCustomView here: With names injectCustomView and injectSecondaryCustomView it would be obvious that only the former is callable from within Foo.
Aside from simply setting an injectedAlready boolean field as a flag, one technique is to create an overridable method, which does not call its superclass implementation:
class CustomView extends View {
#Inject
SomeObject mObject;
#Override
protected void onFinishInflate() {
injectMe();
super.onFinishInflate();
}
protected void injectMe() {
getApplicationComponent().inject(this); // inject(CustomView);
}
}
class SecondaryCustomView extends CustomView {
#Inject
AnotherObject mAnotherObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// ...
}
/** Despite looking identical, the JVM can call the more-specific overload here. */
#Override protected void injectMe() {
getApplicationComponent().inject(this); // inject(SecondaryCustomView)
}
}
If you're looking for a similar solution for Activity and Fragment classes, you can use dagger.android; the built-in mechanism there uses the runtime type of the class to dynamically fetch the right AndroidInjector from a Map. However, that solution doesn't support View at the moment, so this is as close as you can get for your specific case.

Android MVP: which layer should store context variable

I find myself where i need to play a sound file when user clicks a button on a view.
MediaPlayer requires a context to be created.
What is the best way to put MediaPlayer initialization code?
Should I pass a context into a presenter method and play it there?
Or is it ok to just play on the view.
Context is a part of Android View Layer in MVP, so Presenter must not have any idea about it and you should not pass it to presenter.
You have to add a methods to your View interface and implement it inside your android view components (i.e. Activity or Fragment) and use them to do an action in View layer as playing a sound.
Presenter must ask for the UI event and View must handle it!
Here is a MVP sample using Dagger, RxJava and Retrofit, which might help you to learn more about MVP in Android:
https://github.com/mmirhoseini/marvel
I often put business logic code in Model Layer (don't make confusion with model in database). I often rename as XManager for avoiding confusion (such as ProductManager, MediaManager ...) so presenter class just uses for keeping workflow.
The rule of thumb is no or at least limit import android package in presenter class. This best practice supports you easier in testing presenter class because presenter now is just a plain java class, so we don't need android framework for testing those things.
For example here is my mvp workflow.
View class: This is a place you store all your view such as button, textview ... and you set all listeners for those view components on this layer. Also on this View, you define a Listener class for presenter implements later. Your view components will call methods on this listener class.
class ViewImpl implements View {
Button playButton;
ViewListener listener;
public ViewImpl(ViewListener listener) {
// find all view
this.listener = listener;
playButton.setOnClickListener(new View.OnClickListener() {
listener.playSong();
});
}
public interface ViewListener {
playSong();
}
}
Presenter class: This is where you store view and model inside for calling later. Also presenter class will implement ViewListener interface has defined above. Main point of presenter is control logic workflow.
class PresenterImpl extends Presenter implements ViewListener {
private View view;
private MediaManager mediaManager;
public PresenterImpl(View, MediaManager manager) {
this.view = view;
this.manager = manager;
}
#Override
public void playSong() {
mediaManager.playMedia();
}
}
Manager class: Here is the core business logic code. Maybe one presenter will have many managers (depend on how complicate the view is). Often we get Context class through some injection framework such as Dagger.
Class MediaManagerImpl extends MediaManager {
// using Dagger for injection context if you want
#Inject
private Context context;
private MediaPlayer mediaPlayer;
// dagger solution
public MediaPlayerManagerImpl() {
this.mediaPlayer = new MediaPlayer(context);
}
// no dagger solution
public MediaPlayerManagerImpl(Context context) {
this.context = context;
this.mediaPlayer = new MediaPlayer(context);
}
public void playMedia() {
mediaPlayer.play();
}
public void stopMedia() {
mediaPlayer.stop();
}
}
Finally: Put those thing together in Activities, Fragments ... Here is the place you initialize view, manager and assign all to presenter.
public class MyActivity extends Activity {
Presenter presenter;
#Override
public void onCreate() {
super.onCreate();
IView view = new ViewImpl();
MediaManager manager = new MediaManagerImpl(this.getApplicationContext());
// or this. if you use Dagger
MediaManager manager = new MediaManagerImpl();
presenter = new PresenterImpl(view, manager);
}
#Override
public void onStop() {
super.onStop();
presenter.onStop();
}
}
You see that each presenter, model, view is wrapped by one interface. Those components will called through interface. This design will make your code more robust and easier for modifying later.
This is such a long post for answering your question. I think this is suitable because everyone has their own MVP implementation (core flow is same, but minorities are different). So I present here a workflow I often use in work. Hoping you see this useful :)
If you need a generic Context you can extend Application, declare a static context variable and after you can get this Context into your presenter.

Mock objects in Android not passed as parameters

I am trying to test a Fragment I've created in Android. I have complete control of the code, so I can change it as I see fit. The issue is that I'm not sure what design pattern I'm missing to make it reasonable.
I am looking for a way to mock objects in Android that are not passed as parameters. This question suggests that anything you might want to mock should be written to be passed as a parameter.
This makes sense for some situations, but I can't figure out how to get it working on Android, where some of this isn't possible. With a Fragment, for example, you're forced to let much of the heavy lifting be done in callback methods. How can I get my mocked objects into the Fragment?
For example, in this ListFragment I need to retrieve an array of things to display to the user. The things I'm displaying need to be retrieved dynamically and added to a custom adapter. It currently looks as follows:
public class MyFragment extends ListFragment {
private List<ListItem> mList;
void setListValues(List<ListItem> values) {
this.mList = values;
}
List<ListItem> getListValues() {
return this.mList;
}
#Override
public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) {
// blah blah blah
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
this.setListValues(ListFactory.getListOfDynamicValues());
CustomAdapter adapter = new CustomAdapter(
getActivity(),
R.layout.row_layout,
this.getListValues());
this.setListAdapter(adapter);
}
}
I'm trying to do this using Mockito and Robolectric.
This is the beginning of my robolectric test case:
public class MyFragmentTest {
private MyFragment fragment;
#Before
public void setup() {
ListItem item1 = mock(ListItem.class);
ListItem item2 = mock(ListItem.class);
when(item1.getValue()).thenReturn("known value 1");
when(item2.getValue()).thenReturn("known value 2");
List<ListItem> mockList = new ArrayList<ListItem>();
mockList.add(item1);
mockList.add(item2);
MyFragment real = new MyFragment();
this.fragment = spy(real);
when(this.fragment.getValueList()).thenReturn(mockList);
startFragment();
}
}
This feels so very wrong. This section from the mockito api points out that you shouldn't have to do partial mocks like this very frequently unless you're dealing with legacy code.
Further, I'm not actually able to mock out the CustomAdapter class using this approach.
What is the right way to do this sort of thing? Am I structuring things incorrectly in my Fragment classes? I suppose I might be able to add a bunch of package-private setters, but this still doesn't feel right.
Can someone shed some light on this? I'm happy to do rewrites, I just want to know some good patterns for dealing with the state in my Fragments and how I can make them testable.
I ended up creating my own solution to this. My approach was to add another level of indirection to each my calls that create or set an object.
First, let me point out that I couldn't actually get Mockito to work reliably with Fragment or Activity objects. It was somewhat hit or miss, but especially with trying to create Mockito Spy objects, some lifecycle methods appeared to not be called. I think this is related to gotcha number 2 shown here. Perhaps this is due to the ways that Android uses reflection to recreate and instantiate activities and fragments? Note that I was NOT incorrectly holding onto the reference, as it warns of, but interacting only with the Spy, as indicated.
So, I wasn't able to mock Android objects that required lifecycle methods be invoked by the framework.
My solution was to create to more types of methods in my Activity and Fragment methods. These methods are:
getters (getX()) that return the field named X.
retrievers (retrieveX()) that do some sort of work to get an object.
creators (createMyFragment()) that create objects by calling new. Similar to the retrievers.
Getters have whatever visibility you need. Mine are usually public or private.
Retrievers and creators are package private or protected, allowing you to override them in your test packages but not making them generally available. The idea behind these methods is that you can subclass your regular objects with stub objects and inject in known values during testing. You could also just mock out those methods if Mockito mocks/spies are working for you.
Taken in toto, the test would look something like the following.
Here is the fragment from my original question, modified to use the above approach. This is in the normal project:
package org.myexample.fragments
// imports
public class MyFragment extends ListFragment {
private List<ListItem> mList;
void setListValues(List<ListItem> values) {
this.mList = values;
}
List<ListItem> getListValues() {
return this.mList;
}
#Override
public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) {
// blah blah blah
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
this.setListValues(this.retrieveListItems());
CustomAdapter adapter = this.createCustomAdapter();
this.setListAdapter(adapter);
}
List<ListItem> retrieveListItems() {
List<Item> result = ListFactory.getListOfDynamicValues();
return result;
}
CustomAdapter createCustomAdapter() {
CustomAdapter result = new CustomAdapter(
this.getActivity();
R.layout.row_layout,
this.getListValues());
return result;
}
}
When I test this object, I want to be able to control what gets passed around. My first thought was to use a Spy, replacing the return values of retrieveListItems() and createCustomAdapter() with my known values. However, like I said above, I wasn't able to get Mockito spies to behave when working with fragments. (Especially ListFragments--I had mixed success with other types, but don't trust it.) So, we are going to subclass this object. In the test project, I have the following. Note that your method visibility in your real class must allow subclasses to override, so it needs to be package private and in the same package or protected. Note that I am overriding the retriever and creator, returning instead static variables that my tests will set.
package org.myexample.fragments
// imports
public class MyFragmentStub extends MyFragment {
public static List<ListItem> LIST = null;
public static CustomAdapter ADAPTER = null;
/**
* Resets the state for the stub object. This should be called
* in the teardown methods of your test classes using this object.
*/
public static void resetState() {
LIST = null;
ADAPTER = null;
}
#Override
List<ListItem> retrieveListItems() {
return LIST_ITEMS;
}
#Override
CustomAdapter createCustomAdapter() {
return CUSTOM_ADAPTER;
}
}
In the same package in my test project I have the actual test of the fragment. Note that while I'm using Robolectric, this should work with whatever test framework you're using. The #Before annotation becomes less useful, as you need to update your static state for individual tests.
package org.myexample.fragments
// imports
#RunWith(RobolectricTestRunner.class)
public class MyFragmentTest {
public MyFragment fragment;
public Activity activity;
#After
public void after() {
// Very important to reset the state of the object under test,
// as otherwise your tests will affect each other.
MyFragmentStub.resetState();
}
private void setupState(List<ListItem> testList, CustomAdapter adapter) {
// Set the state you want the fragment to use.
MyFragmentStub.LIST = testList;
MyFragmentStub.ADAPTER = adapter;
MyFragmentStub stub = new MyFragmentStub();
// Start and attach the fragment using Robolectric.
// This method doesn't call visible() on the activity, though so
// you'll have to do that yourself.
FragmentTestUtil.startFragment(stub);
Robolectric.ActivityController.of(stub.getActivity()).visible();
this.fragment = stub;
this.activity = stub.getActivity();
}
#Test
public void dummyTestWithKnownValues() {
// This is a test that does nothing other than show you how to use
// the stub.
// Create whatever known values you want to test with.
List<ListItem> list = new ArrayList<ListItem>();
CustomAdapter adapter = mock(CustomAdapter.class);
this.setupState(list, adapter);
// android fest assertions
assertThat(this.fragment).isNotNull();
}
}
This is definitely more verbose than using a mocking framework. However, it works even with Android's life cycle. If I'm testing an Activity, I'll also often include a static boolean BUILD_FRAGMENTS variable. If true, I'll go call through to super in the appropriate methods or return a known fragment as appropriate. In this way I'm able to inject my test objects and play nice with the Android life cycle.

How can I call a function in my main Activity class from a custom Gallery view in Android?

I have a custom gallery view in which I am overriding some methods. I would like to be able to call a function in my main activity from this class. How do I make a reference back to my main class?
I thought I'd just push the class reference into CustomGallery by creating a setter function ---> g.setBaseClass(this);
CustomGallery g = (CustomGallery) findViewById(R.id.playSelectionGallery);
g.setSpacing(10);
g.setCallbackDuringFling(false);
g.setAdapter(new ImageAdapter(this));
g.setSelection(1);
registerForContextMenu(g);
g.setBaseClass(this);
Problem is this is of type Context and someFunctionToCall() will result in a not a member of this class error. In my custom class I have:
public void setBaseClass(Context baseClass)
{
_baseClass = baseClass;
}
private void callSomeFuntionOnMyMainActivityClass()
{
_baseClass.someFunctionToCall();
}
All I want to do is call back to my main class, called ViewFlipperDemo. This would be easy in As3. Any thoughts? Hopefully I'm missing something really simple.
That's actually not a good idea... but you can do it this way:
private void callSomeFuntionOnMyMainActivityClass()
{
((ViewFlipperDemo)_baseClass).someFunctionToCall();
}
What you should do instead is implementing a simple observer which allows you to notify the Activity that something happened. That's one of the main OO principles, your custom class shouldn't know anything about your activity class.
Observer pattern example
The Observer interface:
// TheObserver.java
public interface TheObserver{
void callback();
}
Your custom view:
public class CustomGallery{
private TheObserver mObserver;
// the rest of your class
// this is to set the observer
public void setObserver(TheObserver observer){
mObserver = observer;
}
// here be the magic
private void callSomeFuntionOnMyMainActivityClass(){
if( mObserver != null ){
mObserver.callback();
}
}
// actually, callSomeFuntionOnMyMainActivityClass
// is not a good name... but it will work for the example
}
This is the activity that will benefit of the observer (notice that now you can use your custom view on different activities not just one, that's one of the key reasons to implement it this way):
public class YourActivity extends Activity{
// your normal stuff bla blah
public void someMethod(){
CustomGallery g=(CustomGallery)findViewById(R.id.playSelectionGallery);
g.setObserver(new TheObserver(){
public void callback(){
// here you call something inside your activity, for instance
methodOnYourActivity();
}
});
}
}
You will notice that this design pattern (observer) is widely used in Java and Android... almost any kind of UI event is implemented using observers (OnClickListener, OnKeyListener, etc.). By the way, I didn't test the code, but it should work.

Categories

Resources