Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Phonegap and Fragments

Im trying to implement a Phonegap CordovaWebView into my Fragment, but it doesn't work.

My Layout looks like the following (cordovawebview.xml):

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">

<org.apache.cordova.CordovaWebView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id = "@+id/mainView"/>

</FrameLayout>

In My Fragment's onCreateView() I try to inflate the layout:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

    View v = inflater.inflate(R.layout.cordovawebview, container); // <--- the error occurs here!
    // CordovaWebView webView = (CordovaWebView)v.findViewById(R.id.mainView);

    return v;
}

Maybe someone has some hints how to fix it. I always get that error:

10-25 15:52:02.839: ERROR/AndroidRuntime(2878): FATAL EXCEPTION: main
    android.view.InflateException: Binary XML file line #21: Error inflating class org.apache.cordova.CordovaWebView
    at android.view.LayoutInflater.createView(LayoutInflater.java:613)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
    at com.advantageframework.tabs.fragments.SampleFragmentA.onCreateView(SampleFragmentA.java:81)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:871)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1083)
    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:635)
    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1431)
    at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:420)
    at android.os.Handler.handleCallback(Handler.java:615)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4745)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Constructor.constructNative(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at android.view.LayoutInflater.createView(LayoutInflater.java:587)
    ... 19 more
    Caused by: java.lang.NullPointerException
    at org.apache.cordova.CordovaWebView.loadConfiguration(CordovaWebView.java:643)
    at org.apache.cordova.CordovaWebView.<init>(CordovaWebView.java:131)
    ... 22 more
like image 309
Bins Ich Avatar asked Jan 16 '23 05:01

Bins Ich


2 Answers

I found the answer. If you look in your logcat you see, that it tries to cast the Context to CordovaInterface. The context is the Activity. There it fails. You probably made the Fragment be CordovaInterface. That is what I did. You will have to make the Activity a CordovaInterface and from the Activity forward the events (onMessage, ...) to the Fragment.

Here is my activity (with some stuff removed to make it simpler). I have something very close to this that works pretty well. You will still experience some smaller problems afterwards, but those are easy to solve.

public class MyCordovaActivity extends SherlockFragmentActivity implements
        CordovaInterface {
    private final ExecutorService mThreadPool = Executors.newCachedThreadPool();
    private CordovaPlugin mActivityResultCallback;

    private CordovaFragment mFragment;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.nbu_web_activity);
        mFragment = new CordovaFragment();
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment, mFragment)
                .commit();

        // TODO this value you could pass to the activity with a intent extra
        // or allow to do this through a seperate function, ...
        String url = "http://....";
        mFragment.loadUrl(url);
    }

    @Override
    protected void onNewIntent(final Intent intent) {
        super.onNewIntent(intent);
        NBUGapFragment fragment = getCordovaFragment();
        if (fragment != null && fragment.appView != null) {
            fragment.appView.onNewIntent(intent);
        }
    }

    @Override
    public void onBackPressed() {
        NBUGapFragment fragment = getCordovaFragment();
        if (fragment == null || !fragment.onBackPressed()) {
            super.onBackPressed();
        }
    }

    @Override
    public void cancelLoadUrl() {
        getCordovaFragment().cancelLoadUrl();
    }

    @Override
    public Activity getActivity() {
        return this;
    }

    @Override
    public Context getContext() {
        return this;
    }

    @Override
    public ExecutorService getThreadPool() {
        return mThreadPool;
    }

    @Override
    public Object onMessage(final String id, final Object data) {
        return getCordovaFragment().onMessage(id, data);
    }

    @Override
    public void setActivityResultCallback(final CordovaPlugin plugin) {
        mActivityResultCallback = plugin;
    }

    @Override
    public void startActivityForResult(final CordovaPlugin plugin, final Intent intent,
            final int requestCode) {
        mActivityResultCallback = plugin;
        startActivityForResult(intent, requestCode);
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
        if (mActivityResultCallback != null) {
            mActivityResultCallback.onActivityResult(requestCode, resultCode, intent);
        } else {
            super.onActivityResult(requestCode, resultCode, intent);
        }
    }

    private CordovaFragment getCordovaFragment() {
        // the CordovaFragment is the one implementing CordovaInterface
        return mFragment;
    }
}
like image 60
Patrick Boos Avatar answered Jan 23 '23 08:01

Patrick Boos


CordovaWebView assumes Inflater's context (in this case, main Activity) implements CordovaInterface. There's no interface pass separate object (context and CordovaInterface object) through inflater, you should create proxy context which contain two object and delegate method call to each;

private class CordovaContext extends ContextWrapper implements CordovaInterface
{
    CordovaInterface ci;

    // Hold two objects. Method call to Context will be proxied by ContextWrapper, so only delegate CordovaInterface.
    // You should add/modify when method in CordovaInterface changed
    public CordovaContext(Context base, CordovaInterface ci) {
        super(base);
        this.ci = ci;
    }
    public void startActivityForResult(CordovaPlugin command,
            Intent intent, int requestCode) {
        ci.startActivityForResult(command, intent, requestCode);
    }
    public void setActivityResultCallback(CordovaPlugin plugin) {
        ci.setActivityResultCallback(plugin);
    }
    public Activity getActivity() {
        return ci.getActivity();
    }
    public Object onMessage(String id, Object data) {
        return ci.onMessage(id, data);
    }
    public ExecutorService getThreadPool() {
        return ci.getThreadPool();
    }

}

In Fragment, create inflater which uses this Context object;

LayoutInflater localInflater = inflater.cloneInContext(new CordovaContext(getActivity(), this));
View v = localInflater.inflate(R.layout.cordovawebview, container, false);
like image 38
ToruY Avatar answered Jan 23 '23 07:01

ToruY