box / box-android-preview-sdk

Box Android Preview SDK
Apache License 2.0
13 stars 10 forks source link

Display files on Fragments not on BoxPreviewActivity #10

Open minaEweida opened 7 years ago

minaEweida commented 7 years ago

Hi, I am trying to use box-android-preview-sdk, but I can't find any other way to display my files except using BoxPreviewActivity which pretty much handles everything yet my business case implies that I display the file inside an overlay which means I need to preview the selected file on a fragment not an activity.

What I am trying to do is to display the files using fragments on our application, but this is not possible through the SDK, or so I would think but I can't be sure because there is not enough documentation about this.

Is there any way that I can display the files on a fragment or not? I see that there is BoxPreviewFragment but I could not use it. Or is the only way to preview is using BoxPreviewActivity?

Another thing, we are displaying files that are stored on Box CMS, I already use BoxShareApi and pass the sharedLink to get the BoxItem so that I can use it with the preview SDK yet I always get the following error from the Preview SDK that the file is not found although I already got all its info using BoxShareApi

{"type":"error","status":404,"code":"not_found","context_info":{"errors":[{"reason":"invalid_parameter","name":"item","message":"Invalid value 'f_157150080898'. 'item' with value 'f_157150080898' not found"}]},"help_url":"http:\/\/developers.box.com\/docs\/#errors","message":"Not Found","request_id":"number removed for privacy458fe9dce896a7"}

We tried this approach with Box IOS Preview SDK and it works just fine, I just don't understand what am I missing from Android side? Is there a limitation in the android preview SDK that does not allow me to preview files on CMS unless I at least have collaborator access to it?

doncung commented 7 years ago

You can use the BoxPreviewViewPager and call the loadItem/s method on it. You may also have to set various listeners on the view pager to handle different events from the view pager. For us the logic is in various activity impl methods depending on the different types of content being previewed, but if you know exactly what type of file you are previewing i.e. image or pdf you only need to handle that type. The view pager has a generic listener you can set which will let you know when the fragment is loaded, unloaded, and visible. Then based on the type of fragment (you will need instanceof checks), the types of previewfragments which the view pager loads have various methods that can interact with them.

minaEweida commented 7 years ago

@doncung thank you so much. I will give it a try.

minaEweida commented 7 years ago

Hi @doncung, I tried the BoxPreviewViewPager as advised. Added it to my layout and made sure that the activity implements BoxPreviewExecutorProvider and implemented the api required for it as done in the BoxPreviewActivity

@Override
public BoxPreviewViewPager.BoxPreviewExecutor getPreviewExecutor() {
    if (PREVIEW_EXECUTOR == null) {
        PREVIEW_EXECUTOR = BoxPreviewViewPager.BoxPreviewExecutor.createInstance(getApplication());
    }
    return PREVIEW_EXECUTOR;
}

And then after my service call returns the boxFile I want to preview, I called loadItem and implemented PreviewController that is serializable

            Fragment previewFragment = getFragmentManager().findFragmentByTag("Preview");
            BoxPreviewViewPager boxPreviewViewPager = (BoxPreviewViewPager) previewFragment.getView().findViewById(R.id.box_preview_view_pager);
            boxPreviewViewPager.loadItem(new LighthouseBoxPreviewController(boxSharedLinkSession), boxFile);

// Preview Controller class LighthouseBoxPreviewController implements Serializable, PreviewController {

    private BoxSession session;

    public LighthouseBoxPreviewController(BoxSession session) {
        this.session = session;
    }

    @Override
    public BoxSession getSession() {
        return session;
    }

    @Override
    public PreviewStorage getStorage() {
        return new PreviewStorage(session);
    }

    @Override
    public BoxApiPreview getApiPreview() {
        return new BoxApiPreview(session);
    }

    @Override
    public BoxApiFolder getApiFolder() {
        return new BoxApiFolder(session);
    }

    @Override
    public InputStream downloadThumbnail(BoxFile boxFile, int minSize) throws BoxException {
        return null;
    }
}

However, it still throws the exception I used to get when I tried to load it using BoxPreviewFragment

Process: com.ibm.cio.be.ppm, PID: 27130 android.view.InflateException: Binary XML file line #27: Binary XML file line #27: Error inflating class TextView at android.view.LayoutInflater.inflate(LayoutInflater.java:539) at android.view.LayoutInflater.inflate(LayoutInflater.java:423) at com.box.androidsdk.preview.fragments.BoxPreviewDefaultFragment.onCreateView(BoxPreviewDefaultFragment.java:51) at com.box.androidsdk.preview.fragments.BoxPreviewMediaFragment.onCreateView(BoxPreviewMediaFragment.java:54) at com.box.androidsdk.preview.fragments.BoxPreviewVideoFragment.onCreateView(BoxPreviewVideoFragment.java:92) at android.support.v4.app.Fragment.performCreateView(Fragment.java:2189) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1299) at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595) at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:757) at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2355) at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2146) at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2098) at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:1979) at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:626) at android.support.v4.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:166) at android.support.v4.view.ViewPager.populate(ViewPager.java:1268) at android.support.v4.view.ViewPager.populate(ViewPager.java:1116) at android.support.v4.view.ViewPager.setAdapter(ViewPager.java:540) at com.box.androidsdk.preview.BoxPreviewViewPager$4.run(BoxPreviewViewPager.java:207) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Is there something that I am doing wrong? or Am I missing something?

doncung commented 7 years ago

Alright I'm able to repro in my sample app. Will dig into why this is happening.

doncung commented 7 years ago

So after some investigation this seems to be due to the way the layout is implemented. We'll evaluate a fix for this, but for now what you need to do is add a style to your activity that has at least the following:

<style name="previewsdk_dark_theme" parent="Theme.AppCompat.NoActionBar">
    <item name="boxPrimaryText">@color/box_previewsdk_primary_text_dark</item>
    <item name="boxSecondaryText">@color/box_previewsdk_secondary_text_dark</item>
</style

You should be able to use your own colors if you want, but I tried the following and do not get the crash.

omkarhande-zz commented 5 years ago

New version of the SDK requires implementation of

override fun getBrowseController(): BrowseController { } What is expected here?

lub0s commented 5 years ago

Try returning DefaultPreviewController(session)

omkarhande-zz commented 5 years ago

@lupajz there are two more override fun getNotificationContentIntent(p0: Context?, p1: BoxSession?, p2: BoxFile?, p3:BoxFolder?): Intent {}

override fun execute(p0: Runnable?) {}

What about these?

lub0s commented 5 years ago

What are you actually trying to build @omkarhande ? Are you trying to create a sample where you don't have to run an Activity to preview the BoxFile ?

If that's the case I put up a fork that seems to work with documents (haven't tested rest) https://github.com/lupajz/box-android-preview-sdk/tree/no_activity

If you could provide more details I could try to help.

But to clarify those 2 methods:

  1. getNotificationContentIntent - Should return an intent to start BoxPreviewActivity
  2. execute - should somehow execute the Runnable from parameter
omkarhande-zz commented 5 years ago

@lupajz I am trying to open files in fragment using the BoxPreviewViewPager

lub0s commented 5 years ago

@omkarhande so the sample I've linked above should work for your case. You can see the changes here

omkarhande-zz commented 5 years ago

@lupajz this is what I did, based on your sample code. Getting a blank screen with this.

class FilePreviewFragment : androidx.fragment.app.Fragment() {
    lateinit var session:BoxSession
    private lateinit var boxId: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mixpanel = MixpanelAPI.getInstance(context, BuildConfig.MIXPANEL_TOKEN)
        BoxConfig.CLIENT_ID = BuildConfig.BOX_CLIENT_ID
        BoxConfig.CLIENT_SECRET = BuildConfig.BOX_CLIENT_SECRET

        boxId = FilePreviewFragmentArgs.fromBundle(arguments!!).boxFileId

        session = BoxSession(context!!, BuildConfig.BOX_ACCESSTOKEN, null)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.file_fragment_layout, container, false)
        box_preview_area?.init()
        session.authenticate(context) { response ->
            Logger.d("boxResponse = %s", response.isSuccess)
            val fileApi = BoxApiFile(session)
            var file: BoxFile? = null
            try {
                file = fileApi.getInfoRequest(boxId).send()
                Logger.d("boxfile = %s", file)
                val defaultPreviewController = DefaultPreviewController(session)
                box_preview_area?.fragmentListener = object : BoxPreviewFragment.Listener{
                    override fun onFragmentVisible(p0: BoxPreviewFragment?) {
                    }

                    override fun onFragmentLoaded(p0: BoxPreviewFragment?) {
                    }

                    override fun onFragmentTapped() {
                    }

                    override fun onError(p0: Exception?) {
                        p0?.message?.let { Logger.e(p0, it) }
                    }

                    override fun onFragmentUnloaded(p0: BoxPreviewFragment?) {
                    }

                    override fun onFragmentScrolled() {
                    }

                }

                box_preview_area.loadItem(defaultPreviewController, file)

            } catch (e: BoxException) {
                Logger.e("Boxfile error " + e.response, e)
            }
        }
        return view
    }
}
lub0s commented 5 years ago

@omkarhande Can I also see the xml layout please ? Do you get any logs from your code ? Could you probably create a minimal project for me to clone and debug ?

From just looking at your code I believe that issue is that you are trying to access views using kotlin synthetic reference, namely box_preview_area in wrong lifecycle method. box_preview_area in your case is in fact null because I think it is implemented as getView().findViewById(R.id. box_preview_area) by Kotlin compiler plugin. getView() in your case will result into null since the View will be available only after onViewCreated callback of the Fragment.

Anyway I've uploaded a video sample of what I'm getting from my example.