Permalink
Browse files

Basic support for Fragments

  • Loading branch information...
Aaron VonderHaar & Amrit Thakur
Aaron VonderHaar & Amrit Thakur committed Mar 19, 2012
1 parent 31173f9 commit d1a3090101df9daf574faffe0c3ac84a8217f608
View
1 .pairs
@@ -26,6 +26,7 @@ pairs:
ad: Andrew Dai
bc: Brian Colvin
av: Aaron VonderHaar; avonderhaar
+ at: Amrit Thakur
email:
prefix: pair
domain: pivotallabs.com
View
@@ -55,6 +55,7 @@
<pathelement path="${sdk.dir}/platforms/android-10/android.jar"/>
<pathelement path="${sdk.dir}/add-ons/addon_google_apis_google_inc_10/libs/maps.jar"/>
<pathelement path="${sdk.dir}/add-ons/addon-google_apis-google_inc_-10/libs/maps.jar"/>
+ <pathelement path="${sdk.dir}/extras/android/support/v4/android-support-v4.jar"/>
</path>
<property name="main.absolute.dir" location="${main.dir}"/>
View
@@ -88,6 +88,13 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>support-v4</artifactId>
+ <version>r6</version>
@jberkel

jberkel Mar 20, 2012

Collaborator

is this the correct scope? tests don't start with the current snapshot unless this jar is present.

@avh4

avh4 Mar 20, 2012

Collaborator

Maven should still download the jar when running robolectric's tests.. is that not the case? To run tests with ant you will need to have the support jar downloaded via the android SDK tool.

@jberkel

jberkel Mar 20, 2012

Collaborator

it won't get downloaded automatically if the scope is provided, here's the output from mvn dependency:tree

[INFO] +- com.pivotallabs:robolectric:jar:1.1-SNAPSHOT:test
[INFO] |  +- org.objenesis:objenesis:jar:1.0:test
[INFO] |  +- org.xerial:sqlite-jdbc:jar:3.7.2:test
[INFO] |  \- org.javassist:javassist:jar:3.14.0-GA:test
@avh4

avh4 Mar 21, 2012

Collaborator

It does seem to download:

robolectric (master)$ rm -Rf ~/.m2/repository/com/google/android/support-v4
robolectric (master)$ mvn clean test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Robolectric 1.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: http://repo1.maven.org/maven2/com/google/android/support-v4/r6/support-v4-r6.pom
Downloaded: http://repo1.maven.org/maven2/com/google/android/support-v4/r6/support-v4-r6.pom (4 KB at 5.0 KB/sec)
Downloading: http://repo1.maven.org/maven2/com/google/android/support-v4/r6/support-v4-r6.jar
Downloaded: http://repo1.maven.org/maven2/com/google/android/support-v4/r6/support-v4-r6.jar (242 KB at 340.6 KB/sec)

@jberkel

jberkel Mar 21, 2012

Collaborator

it downloads in the robolectric repo itself, but not when robolectric-snapshot is referenced from another project's pom. also don't see why it should be provided scope since the v4 jar is never present on any device, and it's needed to compile and run the tests.

@avh4

avh4 Mar 21, 2012

Collaborator

Okay, I can reproduce this now, thanks!

See pivotal#216

@avh4

avh4 Mar 21, 2012

Collaborator

As a final note (since I was confused about this myself), ant builds of projects referencing robolectric will still work, since robolectric-1.1-SNAPSHOT-jar-with-dependencies.jar includes the classes from support-v4.

+ <scope>provided</scope>
+ </dependency>
+
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
@@ -28,6 +28,8 @@
import android.net.wifi.WifiManager;
import android.os.*;
import android.preference.*;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
@@ -177,6 +179,8 @@ public static void logMissingInvokedShadowMethods() {
ShadowExpandableListView.class,
ShadowFilter.class,
ShadowFloatMath.class,
+ ShadowFragment.class,
+ ShadowFragmentActivity.class,
ShadowFrameLayout.class,
ShadowGallery.class,
ShadowGeocoder.class,
@@ -530,6 +534,14 @@ public static ShadowFilter shadowOf(Filter instance) {
return (ShadowFilter) shadowOf_(instance);
}
+ public static ShadowFragment shadowOf(Fragment instance) {
+ return (ShadowFragment) shadowOf_(instance);
+ }
+
+ public static ShadowFragmentActivity shadowOf(FragmentActivity instance) {
+ return (ShadowFragmentActivity) shadowOf_(instance);
+ }
+
public static ShadowFrameLayout shadowOf(FrameLayout instance) {
return (ShadowFrameLayout) shadowOf_(instance);
}
@@ -1,10 +1,13 @@
package com.xtremelabs.robolectric.res;
import android.content.Context;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.widget.FrameLayout;
import com.xtremelabs.robolectric.tester.android.util.TestAttributeSet;
import com.xtremelabs.robolectric.util.I18nException;
import org.w3c.dom.Document;
@@ -167,6 +170,10 @@ private View create(Context context, ViewGroup parent) throws Exception {
return view;
} else if (name.equals("merge")) {
return parent;
+ } else if (name.equals("fragment")) {
+ View fragment = constructFragment(context);
+ addToParent(parent, fragment);
+ return fragment;
} else {
applyFocusOverride(parent);
View view = constructView(context);
@@ -176,6 +183,33 @@ private View create(Context context, ViewGroup parent) throws Exception {
}
}
+ private FrameLayout constructFragment(Context context) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+ TestAttributeSet attributeSet = new TestAttributeSet(attributes, resourceExtractor, attrResourceLoader, View.class, isSystem);
+ if (strictI18n) {
+ attributeSet.validateStrictI18n();
+ }
+
+ Class<? extends Fragment> clazz = loadFragmentClass(attributes.get("android:name"));
+ Fragment fragment = ((Constructor<? extends Fragment>) clazz.getConstructor()).newInstance();
+ if (!(context instanceof FragmentActivity)) {
+ throw new RuntimeException("Cannot inflate a fragment unless the activity is a FragmentActivity");
+ }
+
+ FragmentActivity activity = (FragmentActivity) context;
+ shadowOf(fragment).setActivity(activity);
+
+ String tag = attributeSet.getAttributeValue("android", "tag");
+ int id = attributeSet.getAttributeResourceValue("android", "id", 0);
+ activity.getSupportFragmentManager().beginTransaction().add(id, fragment, tag).commit();
+
+ View view = fragment.getView();
+
+ FrameLayout container = new FrameLayout(context);
+ container.setId(id);
+ container.addView(view);
+ return container;
+ }
+
private void addToParent(ViewGroup parent, View view) {
if (parent != null && parent != view) {
parent.addView(view);
@@ -200,18 +234,18 @@ private View constructView(Context context) throws InstantiationException, Illeg
}
private Class<? extends View> pickViewClass() {
- Class<? extends View> clazz = loadClass(name);
+ Class<? extends View> clazz = loadViewClass(name);
if (clazz == null) {
- clazz = loadClass("android.view." + name);
+ clazz = loadViewClass("android.view." + name);
}
if (clazz == null) {
- clazz = loadClass("android.widget." + name);
+ clazz = loadViewClass("android.widget." + name);
}
if (clazz == null) {
- clazz = loadClass("android.webkit." + name);
+ clazz = loadViewClass("android.webkit." + name);
}
if (clazz == null) {
- clazz = loadClass("com.google.android.maps." + name);
+ clazz = loadViewClass("com.google.android.maps." + name);
}
if (clazz == null) {
@@ -220,15 +254,24 @@ private View constructView(Context context) throws InstantiationException, Illeg
return clazz;
}
- private Class<? extends View> loadClass(String className) {
+ private Class loadClass(String className) {
try {
- //noinspection unchecked
- return (Class<? extends View>) getClass().getClassLoader().loadClass(className);
+ return getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
return null;
}
}
+ private Class<? extends View> loadViewClass(String className) {
+ // noinspection unchecked
+ return (Class<? extends View>) loadClass(className);
+ }
+
+ private Class<? extends Fragment> loadFragmentClass(String className) {
+ // noinspection unchecked
+ return (Class<? extends Fragment>) loadClass(className);
+ }
+
public void applyFocusOverride(ViewParent parent) {
if (requestFocusOverride) {
View ancestor = (View) parent;
@@ -0,0 +1,42 @@
+package com.xtremelabs.robolectric.shadows;
+
+import android.content.Intent;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.View;
+import com.xtremelabs.robolectric.internal.Implementation;
+import com.xtremelabs.robolectric.internal.Implements;
+
+@Implements(Fragment.class)
+public class ShadowFragment {
+ private View view;
+ private FragmentActivity activity;
+
+ public void setView(View view) {
+ this.view = view;
+ }
+
+ @Implementation
+ public View getView() {
+ return view;
+ }
+
+ public void setActivity(FragmentActivity activity) {
+ this.activity = activity;
+ }
+
+ @Implementation
+ public FragmentActivity getActivity() {
+ return activity;
+ }
+
+ @Implementation
+ public void startActivity(Intent intent) {
+ activity.startActivity(intent);
+ }
+
+ @Implementation
+ public void startActivityForResult(Intent intent, int requestCode) {
+ activity.startActivityForResult(intent, requestCode);
+ }
+}
@@ -0,0 +1,26 @@
+package com.xtremelabs.robolectric.shadows;
+
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import com.xtremelabs.robolectric.internal.Implementation;
+import com.xtremelabs.robolectric.internal.Implements;
+import com.xtremelabs.robolectric.internal.RealObject;
+import com.xtremelabs.robolectric.tester.android.util.TestFragmentManager;
+
+@Implements(FragmentActivity.class)
+public class ShadowFragmentActivity extends ShadowActivity {
+ @RealObject
+ FragmentActivity realObject;
+
+ private FragmentManager fragmentManager;
+
+ public void __constructor__() {
+ fragmentManager = new TestFragmentManager(realObject);
+ }
+
+ @Implementation
+ public FragmentManager getSupportFragmentManager() {
+ return fragmentManager;
+ }
+
+}
@@ -0,0 +1,131 @@
+package com.xtremelabs.robolectric.tester.android.util;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.xtremelabs.robolectric.Robolectric.shadowOf;
+
+public class TestFragmentManager extends FragmentManager {
+ private Map<Integer, Fragment> fragmentsById = new HashMap<Integer, Fragment>();
+ private Map<String, Fragment> fragmentsByTag = new HashMap<String, Fragment>();
+ private FragmentActivity activity;
+
+ public TestFragmentManager(FragmentActivity activity) {
+ this.activity = activity;
+ }
+
+ @Override
+ public FragmentTransaction beginTransaction() {
+ return new TestFragmentTransaction(this);
+ }
+
+ @Override
+ public boolean executePendingTransactions() {
+ return false;
+ }
+
+ @Override
+ public Fragment findFragmentById(int id) {
+ return fragmentsById.get(id);
+ }
+
+ @Override
+ public Fragment findFragmentByTag(String tag) {
+ return fragmentsByTag.get(tag);
+ }
+
+ @Override
+ public void popBackStack() {
+ }
+
+ @Override
+ public boolean popBackStackImmediate() {
+ return false;
+ }
+
+ @Override
+ public void popBackStack(String name, int flags) {
+ }
+
+ @Override
+ public boolean popBackStackImmediate(String name, int flags) {
+ return false;
+ }
+
+ @Override
+ public void popBackStack(int id, int flags) {
+ }
+
+ @Override
+ public boolean popBackStackImmediate(int id, int flags) {
+ return false;
+ }
+
+ @Override
+ public int getBackStackEntryCount() {
+ return 0;
+ }
+
+ @Override
+ public BackStackEntry getBackStackEntryAt(int index) {
+ return null;
+ }
+
+ @Override
+ public void addOnBackStackChangedListener(OnBackStackChangedListener listener) {
+ }
+
+ @Override
+ public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) {
+ }
+
+ @Override
+ public void putFragment(Bundle bundle, String key, Fragment fragment) {
+ }
+
+ @Override
+ public Fragment getFragment(Bundle bundle, String key) {
+ return null;
+ }
+
+ @Override
+ public Fragment.SavedState saveFragmentInstanceState(Fragment f) {
+ return null;
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ }
+
+ public void addFragment(int containerViewId, String tag, Fragment fragment, boolean replace) {
+ fragmentsById.put(containerViewId, fragment);
+ fragmentsByTag.put(tag, fragment);
+
+ fragment.onAttach(activity);
+ fragment.onCreate(null);
+ ViewGroup container = null;
+ if (shadowOf(activity).getContentView() != null) {
+ container = (ViewGroup) activity.findViewById(containerViewId);
+ }
+
+ View view = fragment.onCreateView(activity.getLayoutInflater(), container, null);
+ if (container != null) {
+ if (replace) {
+ container.removeAllViews();
+ }
+ container.addView(view);
+ }
+
+ shadowOf(fragment).setView(view);
+ }
+}
Oops, something went wrong.

0 comments on commit d1a3090

Please sign in to comment.