Use external application fragment/activity inside application
May be a little bit late, but still feel that it can be added and might help others.
For activity, there's really no point to have it embedded, there's convenient way to use other apps activities - start it with intent. For fragments at might make sense in case of implementation some kind of 'plug-in' functionality inside the app.
There's an official way to use code from other applications (or load code from network) in Android Blog 'Custom Class Loading in Dalvik'. Please note, the android is not much different from other platforms/environments, so both parts (your app and fragment You want load into your app) should support some kind of contract. That means You cannot load any component from any application, which is quite common and there are number of reasons for it to be that way.
So, here's some small example of the implementation. It consists of 3 parts:
Interfaces project - this project contains definitions of interfaces which should be loaded by main app in order to use external classes:
package com.example.test_interfaces; import android.app.Fragment; /** * Interface of Fragment holder to be obtained from external application */ public interface FragmentHolder { Fragment getFragment(); }
For this example we need only single interface just to demonstrate how to load the fragment.
Plug-in application, which contains the code You need to load - in our case it's a fragment. Please note, that this project in your IDE should depend on Interface one using 'provided' type and without exporting, because it will be imported by main application.
Fragment, we're going to load PlugInFragment:
package com.sandrstar.plugin; import com.example.test_interfaces.FragmentHolder; public class PlugInFragment extends Fragment implements FragmentHolder { @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { // Note that loading of resources is not the same as usual, because it loaded actually from another apk final XmlResourceParser parser = container.getContext().getPackageManager().getXml("com.sandrstar.plugin", R.layout.fragment_layout, null); return inflater.inflate(parser, container, false); } @Override public Fragment getFragment() { return this; } }
And it's layout fragment_layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/black"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is from fragment" android:textColor="@android:color/white"/> </LinearLayout>
Main application which wants to load the fragment from another application. It should have Interface project imported:
Activity itself MyActivity:
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); try { Class<?> requiredClass = null; final String apkPath = getPackageManager().getApplicationInfo("com.sandrstar.plugin",0).sourceDir; final File dexTemp = getDir("temp_folder", 0); final String fullName = "com.sandrstar.plugin.PlugInFragment"; boolean isLoaded = true; // Check if class loaded try { requiredClass = Class.forName(fullName); } catch(ClassNotFoundException e) { isLoaded = false; } if (!isLoaded) { final DexClassLoader classLoader = new DexClassLoader(apkPath, dexTemp.getAbsolutePath(), null, getApplicationContext().getClassLoader()); requiredClass = classLoader.loadClass(fullName); } if (null != requiredClass) { // Try to cast to required interface to ensure that it's can be cast final FragmentHolder holder = FragmentHolder.class.cast(requiredClass.newInstance()); if (null != holder) { final Fragment fragment = holder.getFragment(); if (null != fragment) { final FragmentTransaction trans = getFragmentManager().beginTransaction(); trans.add(R.id.fragmentPlace, fragment, "MyFragment").commit(); } } } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
And it's layout main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent" android:clipChildren="false" android:id="@+id/root"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/down_image" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/fragmentPlace" android:layout_centerInParent="true" /> </RelativeLayout>
And the finally we able to observe the following on the real device:
Possible issues handling (thanks to @MikeMiller for the update):
- If you get the following error in the call to
classLoader.loadClass
:
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
Make sure the fragment modules are included in the main app (as 'compiled')
If you get a
NameNotFoundException
in the call tocontext.getPackageManager().getApplicationInfo(packageName,0).sourceDir
, then make sure the fragment is in an installed APPLICATION (not just a library dependency). Follow the steps below to make sure that's the case:1) In the main application's build.gradle, change
apply plugin: 'android-library'
toapply plugin: 'android'
and make sure there's a dummy activity java file. In the main application, remove the dependency on the fragment module (It's not specified in step 3, but I had to add a dependency on the fragment module to the main application. But the fragment module is now an activity application, and you can't have dependencies on those) or you'll get this:Error:Dependency unspecified on project resolves to an APK archive which is not supported as a compilation dependency.
2) Run the fragment module (which you can do now, because it's an activity application). That installs it in a way that the getApplicationInfo call can find it Revert build.gradle and add the dependency back in the main app (as a 'compile' dependency) Everything should work now. When you make updates to the fragment code, you won't need to go through this process again. You will, though, if you want to run on a new device or if you add a new fragment module. I hope this is able to save someone the time I spent trying to resolve the above errors.
Android L
Seems, based on normal multidex support with Android L, above steps are not needed, because class loading is different. Approach, described in multidex support can be used instead of Android Blog 'Custom Class Loading in Dalvik', because it clearly states that:
Note: The guidance provided in this document supersedes the guidance given in the Android Developers blog post Custom Class Loading in Dalvik.
Probably, changes in android.support.multidex
might be needed to reuse that approach.
No, you can not "reuse" code from other applications. The only official way is to use Intent
to invoke the whole Activity.