Fresco: Share Element Gone after do SharedElementReturnTransition in Android N

Created on 31 Aug 2016  ·  70Comments  ·  Source: facebook/fresco

I use the newest fresco code for doing SharedElement Transition work, it runs well in Android L、M, But on Android N, it goes wrong

before transition
screenshot0

after transition
screenshot0

bug help wanted

Most helpful comment

Just add this code in calling activity,+1 if this can help you!!! @oprisnik


setExitSharedElementCallback(new SharedElementCallback() {

            @Override
            public void onSharedElementEnd(List<String> sharedElementNames,
                                           List<View> sharedElements,
                                           List<View> sharedElementSnapshots) {

                super.onSharedElementEnd(sharedElementNames, sharedElements,
                        sharedElementSnapshots);

                for (View view : sharedElements) {
                    if (view instanceof SimpleDraweeView) {
                        view.setVisibility(View.VISIBLE);
                    }
                }
            }
        });

All 70 comments

Can you please show how you do the transition part using Fresco's demo app?

activity_main.xml and activity_detail has same SimpleDraweeView with same transitionName
and in DetailActivity’s onCreate method as below:

protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
getWindow().setSharedElementEnterTransition(DraweeTransition.createTransitionSet(ScalingUtils.ScaleType.CENTER_CROP, ScalingUtils.ScaleType.FIT_CENTER));
getWindow().setSharedElementReturnTransition(DraweeTransition.createTransitionSet(ScalingUtils.ScaleType.FIT_CENTER, ScalingUtils.ScaleType.CENTER_CROP));
addTransitionListener();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
mBaselineJpegView = (SimpleDraweeView) findViewById(R.id.baseline_jpeg);
mBaselineJpegView.setImageURI(Uri.parse("https://www.gstatic.com/webp/gallery/1.sm.jpg"));
mBaselineJpegView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(DetailActivity.this, "dsasa", Toast.LENGTH_LONG).show();
}
});
}

addTransitionListener() does nothing just print log

so do I use wrong way to do the transition? 

This is happening to me as well.

I have the following in onCreate() method before super.onCreate()

supportRequestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS);
Transition fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
Window window = getWindow();
window.setEnterTransition(fade);
window.setReturnTransition(fade);
window.setExitTransition(fade);
TransitionSet transitionSet = DraweeTransition
                    .createTransitionSet(ScalingUtils.ScaleType.CENTER_CROP,
ScalingUtils.ScaleType.CENTER_CROP);
window.setSharedElementEnterTransition(transitionSet);
window.setSharedElementExitTransition(transitionSet);

I'm experiencing the same issue. The setSharedElementReturnTransition is not working on Android N. I've added logs to the transition listeners and the onTransitionStart and onTransitionEnd are not triggered on Android N, but on Android M & L it works just fine.
I'm using the latest version of the Fresco library com.facebook.fresco:fresco:0.14.1 with com.facebook.fresco:imagepipeline-okhttp3:0.14.1
I don't have this issue if I use Picasso library for loading the image.
The scenario is similar: gridView, gridItem navigates away to another Activity (everything is fine on this part) but when I go back, there is no reenter, the image flashes and is reloaded (the placeholder image is not visible nor the image that was already loaded). This only happens on Android N (7.0)

Any update on this issue?

Switched from Fresco to Picasso and the issue is resolved. Too bad this library isn't maintained as often as required by the new Android OS versions that come out.

It seems that this issue is incorrectly marked as 'needs-details'. This problem also exists on Android 7.1.1 (API level 25).

Thanks, we'll take a look

I am also experiencing the same problem, so +1 from me for fixing it.

same here

Do it. Do it. ;-)

Yes, please do it.

Please...

+1

Wonder why this isn't a high priority..

+1

When I scroll to the shared element position in onActivityReenter the disappeared item is coming back though with a little white flickering:

    @Override
    public void onActivityReenter(int resultCode, Intent data) {
        super.onActivityReenter(resultCode, data);

        final int position = data.getIntExtra(EXTRA_GIF_POSITION, -1);
        if (resultCode == RESULT_OK && position != -1) {
            gifRecyclerView.getLayoutManager().scrollToPosition(position);
        }
    }

If someone knows a better fix I would appreciate if you could share it.

Hello, i am experiencing the same issue but only on Android 7.1.1 API 25

If there are any updates about the issue i would highly appreciate it.

same here on emu with 7.x, but I use onWindowFocusChanged be immersive, when finish occured the image will disappear but reshow instantly.

Same here. Calling requestLayout on the View containing the SimpleDraweeView seems fixing it with some flickering. Please advise

Just add this code in calling activity,+1 if this can help you!!! @oprisnik


setExitSharedElementCallback(new SharedElementCallback() {

            @Override
            public void onSharedElementEnd(List<String> sharedElementNames,
                                           List<View> sharedElements,
                                           List<View> sharedElementSnapshots) {

                super.onSharedElementEnd(sharedElementNames, sharedElements,
                        sharedElementSnapshots);

                for (View view : sharedElements) {
                    if (view instanceof SimpleDraweeView) {
                        view.setVisibility(View.VISIBLE);
                    }
                }
            }
        });

same problem here... @antxyz I've put your code in both calling and callee activities onCreate method but with no success. The shared element image is still invisible after returning transition.

I don't know If it can help to find a solution, but I noticed that when softkeyboard is opened, the SimpleDraweeView become visible.

I was having the same problem and it was fixed after this two calls:

  1. Just before startActivity(...:
    setExitSharedElementCallback(new SharedElementCallback() {
                @Override
                public void onSharedElementEnd(List<String> names,
                                               List<View> elements,
                                               List<View> snapshots) {
                    super.onSharedElementEnd(names, elements, snapshots);
                    for (final View view : elements) {
                        if (view instanceof SimpleDraweeView) {
                            view.post(() -> view.setVisibility(View.VISIBLE));
                        }
                    }
                }
            });
  1. Inside the onCreate of the new activity:
setEnterSharedElementCallback(new SharedElementCallback() {
                @Override
                public void onSharedElementEnd(List<String> names,
                                               List<View> elements,
                                               List<View> snapshots) {
                    super.onSharedElementEnd(names, elements, snapshots);
                    for (final View view : elements) {
                        if (view instanceof SimpleDraweeView) {
                            view.post(() -> view.setVisibility(View.VISIBLE));
                        }
                    }
                }
            });

@oprisnik any updates on this? It's a pity it hasn't been fixed yet.
I'm still experiencing this with API 24+, when the shared element is an item inside a RecyclerView.
None of the workarounds listed on this ticket worked for me (the view is never set to invisible/gone in the first place).
I'm using DraweeTransition.createTransitionSet() for both enter and return transition.
Sometimes the image disappears until the recyclerview is scrolled, sometimes it comes back with a flicker after a while (might be due to a notifyDataSetChanged() being called).

Not sure if this is of any help, but this is what's happening in Fresco according to the logs when the second activity gets closed and the shared element returns to its position:

V/unknown:AbstractDraweeController: controller c6a03e3 9: onDetach
V/unknown:AbstractDraweeController: controller c6a03e3 9: release: image: CloseableReferenceWithFinalizer f25d513
V/unknown:AbstractDraweeController: controller f84c24e 4: onAttach: request needs submit
V/unknown:AbstractDraweeController: controller f84c24e 4: set_final_result @ onNewResult: image: CloseableReferenceWithFinalizer f25d513

This happens when the bitmap is shown again.
When the bitmap is not shown, the last two events don't occur:

V/unknown:AbstractDraweeController: controller f84c24e 4: onAttach: request needs submit
V/unknown:AbstractDraweeController: controller f84c24e 4: set_final_result @ onNewResult: image: CloseableReferenceWithFinalizer f25d513

@marcosalis When returning to the first activity (with the recyclerView). Does that always trigger a notifyDataSetChanged()?

@erikandre thank you for the answer ;)
After some tests, I can confirm that notifyDataSetChanged() is not called, even when the bitmap is shown on its own.

This is the state of the SimpleDraweeView if I put a breakpoint in SharedElementCallback.onSharedElementEnd() in the calling activity.

 DraweeHolder{controllerAttached=false, holderAttached=true, drawableVisible=false}

PipelineDraweeController{super=PipelineDraweeController{isAttached=false, isRequestSubmitted=false, hasFetchFailed=false, fetchedImage=0, events=[...]}, dataSourceSupplier={request=ImageRequest{uri=https://OMITTED, cacheChoice=DEFAULT, decodeOptions=100-false-false-false-false-ARGB_8888-null, postprocessor=null, priority=HIGH, resizeOptions=null, rotationOptions=-1 defer:true, mediaVariations=null}}}

I think it might have something to do with the doDetach() and doAttach() methods in SimpleDraweeView, when the view itself is in a RecyclerView.
Finally, this worked for me (added to the Activity where the transition generates from), although it's not extremely clean:

        setExitSharedElementCallback(new SharedElementCallback() {
            @Override
            public void onSharedElementEnd(List<String> names, List<View> elements, List<View> snapshots) {
                for (View view : elements) {
                    if (view instanceof SimpleDraweeView) {
                        view.post(() -> {
                                myAdapter.notifyDataSetChanged();
                        });
                    }
                }
            }
        });

@marcosalis Great that you've found a workaround! I tried to reproduce this particular issue in the Showcase app running on Genymotion with Android 7 but no luck.

What does the item views for the recyclerview look like? In my case it was just a FrameLayout wrapping a SimpleDraweeView.

@marcosalis your solution worked for me with a little change, removing the for cycle:

setExitSharedElementCallback(
                    new SharedElementCallback() {
                        @Override
                        public void onSharedElementEnd(List<String> names, List<View> elements, List<View> snapshots) {
                            super.onSharedElementEnd(names, elements, snapshots);
                            notifyDataSetChanged();
                        }
                    }
            );

I've just stumbled on the same issue while playing around with inter-activity transitions. None of the workarounds proposed above seemed to work. (API 25) Here's what it looks like: https://vimeo.com/225497240

I think we found a workaround and hopefully a fix will be available soon.

The workaround can be found in dc7ec2436fc9b639fe1a39398712c360b16da468.

For now, you have to set up the first DraweeView where the transition is starting from to use legacy visibility handling by calling simpleDraweeView.setLegacyVisibilityHandlingEnabled(true); (as you can see in the transition example in our Showcase sample app).

Can you please check if this solves the issues for you? We're going to release a new Fresco version that cointains this fix very soon.

Yep, thanks! Confirmed that this solves the issue. Looking forward to an updated version.

We've released Fresco v1.4.0, which includes the fix as mentioned above. Just call simpleDraweeView.setLegacyVisibilityHandlingEnabled(true); and it should work now.

We've also updated the Showcase transition sample to reflect these changes.

Thanks for this fix, it worked perfect in my case. One question, why don't put setGlobalLegacyVisibilityHandlingEnabled to true by default? There's any performance problem?

Visibility handling changed with Android N and my change is (kind of) reverting the behavior back to pre-N. Changing this behavior for all images could lead to other unexpected issues and that's why we decided to not turn it on by default yet. Once we've verified that there are no side effects, we'll consider turning it on by default.
Feel free to set it totrue and report back any issues that you encounter.

ezgif com-resize

This fix seems to not work randomly. I'm setting setGlobalLegacyVisibilityHandlingEnabled to true, but after a few clicks, the returned image is blank.

Fresco 1.5.0, Oreo

Edit: After developing a couple more days and moving some views around I've found the issue doesn't happen at all anymore on my Oreo pixel device. On my emulated Nexus 5x API 24, the image is always blank after the exit animation.

I'm having the same issue with setLegacyVisibilityHandlingEnabled. Setting it to true works most of the times but sometimes, just randomly, the image disappears after the return transition.

@marcosalis does it only happen on Oreo or also on Nougat?

Hi @oprisnik I've managed to reproduce it randomly on a Samsung S7 (Nougat) and on an emulator on API 26. Let me know if I can be of help to debug it.

Thanks, I'll try to repro when I have a bit more time

@oprisnik
Can confirm it does not seem to do its job at all most of the time (API24 + API25)

However i managed to get it somewhat working using the following hack:

        setExitSharedElementCallback(new SharedElementCallback() {
            @Override
            public void onSharedElementEnd(List<String> names,
                                           List<View> elements,
                                           List<View> snapshots) {
                super.onSharedElementEnd(names, elements, snapshots);
                for (final View view : elements) {
                    if (view instanceof SimpleDraweeView) {
                        view.post(() -> {
                            view.setVisibility(View.VISIBLE);
                            view.requestLayout();
                        });
                    }
                }
            }
        });

I was able to repro. For me it only occurs rarely though. I'll check if I can find a different workaround to fix this.

Hello @oprisnik,
Did you find a different workaround ?

Not yet, this is tricky to fix since Google changed the View visibility behavior, which breaks Fresco's visibility handling (which we need for memory management) and I'm also not going to have a lot of time to look into this in the near future. However, there are several alternatives mentioned in this thread that fix the issue (like the workaround from @MartB that manually sets the visibility).

If somebody would like to look into this, pull reuqests are always welcome! :)

@oprisnik , react-native-maps module also affected from this issue.
I've marked these lines as comment in RootDrawable.java file as a workaround.
Do you remember/know any big issue that is related marking those lines as comment lines?

Experiencing same issue as @efkan looks like it's affecting a large number of Android devices (Android Nougat 7.1 API 25 and up).

Their solution has fixed all the issues I was having.

@efkan I don't know exactly. I remember that I looked into this as well when I was looking for alternative fixes, but dismissed it. I think one issue with removing these checks is that the underlying bitmap has been released (since the View is not visible any more) and trying to draw it would throw a java.lang.RuntimeException: Canvas: trying to use a recycled bitmap ... since BitmapDrawable doesn't perform any sanity checks.

ImageView itself has the same bug where it doesn't properly restore the Drawable visibility of any drawable, but it still works since most Drawables don't do the visibility check and there's no manual memory management - and the underlying Bitmap would still be valid.

@oprisnik thanx for the all these explanations.

In order to test if this works, it's fairly easy to add this as a setting to GenericDraweeHierarchyBuilder and depending on that ignore the visibility check in RootDrawable (which is created in GenericDraweeHierarchy, see https://github.com/facebook/fresco/blob/master/drawee/src/main/java/com/facebook/drawee/generic/GenericDraweeHierarchy.java#L155).

Feel free to submit a PR that adds this setting so that you can enable it for your applications and see if there are side effects.

The same issue appears only on devices with Android 7.1 (or above).
None of above solutions work.
The issue always appears when the network is limited to 10kb/s.

@babyblue1314 - The RootDrawable change mentioned above also doesn't fix it for you?

@oprisnik Thanks for your quick reply!
Em…I suppose so, the RootDrawable change mentioned above also doesn't fix it for me.
I don't know if I wrongly used it. Here is what it looks like:

Before using the RootDrawable change:

After using the RootDrawable change:

_I should mention that the network is limited to 10kb/s._
And what's more, this not only appears in ListView but also RecyclerView and GridView, as shown below:

My Code is:

  1. ListViewAdapter (or RecyclerViewAdapter or GridViewAdapter):
SimpleDraweeView thumbnail = cholder.getView(R.id.thumbnail);

GenericDraweeHierarchyBuilder hierarchyBuilder = new GenericDraweeHierarchyBuilder(thumbnail.getContext().getResources());
GenericDraweeHierarchy hierarchy = hierarchyBuilder.build();
hierarchy.getTopLevelDrawable().setVisible(true, false);
thumbnail.setHierarchy(hierarchy);

ImageLoaderUtil.loadImage(thumbnail, item.getLogourl(), 696, 288);
  1. ImageLoaderUtil:
Uri uri = Uri.parse(url);
ImageRequestBuilder imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(uri);
imageRequestBuilder.setRotationOptions(RotationOptions.autoRotate()); 
imageRequestBuilder.setImageDecodeOptions(new ImageDecodeOptions(ImageDecodeOptions.newBuilder()
.setBitmapConfig(Bitmap.Config.RGB_565)));
imageRequestBuilder.setResizeOptions(new ResizeOptions(reqWidth, reqHeight));
ImageRequest imageRequest = imageRequestBuilder.build();

PipelineDraweeControllerBuilder draweeControllerBuilder = Fresco.newDraweeControllerBuilder();
draweeControllerBuilder.setOldController(simpleDraweeView.getController());
draweeControllerBuilder.setImageRequest(imageRequest);
draweeControllerBuilder.setTapToRetryEnabled(false); 

DraweeController draweeController = draweeControllerBuilder.build();
simpleDraweeView.setController(draweeController);

Fresco 1.5.0

Device: Lenovo, android 7.1.1

_Please let me konw if you need more information. Thanks again._

@babyblue1314 I don't see any transition in your videos or transition-related code. Are you sure that you're talking about the same issue?

@oprisnik Sorry, I just realized that this transition issue is different from my problem.
I saw the phenomenon is similar to mine, so……
Should I start a new issue?

@babyblue1314 yes please, this seems quite unrelated.

@oprisnik I finally found the cause of my problem.
In case of misunderstanding other people, I'm here to give some explanations.
My issue has sth to do with the picture format.
If I use the method imageRequestBuilder.setProgressiveRenderingEnabled(true) with PNG format, my issue can always reproduce.
If I change the method to imageRequestBuilder.setProgressiveRenderingEnabled(false) with PNG format, everything works fine.
@oprisnik Thanks again for your patience!

Please fix this issue...

Fresco 1.9.0 doesn't fix the issue properly.
A mix of the propositions in this thread fixes it depending on the device:

So I'm using both workarounds in my code to be safe in my code and hopefully that covers everything. @oprisnik I'll spend some time to try and understand the problem better, if I find a fix I'll submit a PR.

Yeah, unfortunately we still don't have a good fix for this issue since all solutions mentioned in this thread fail in one or the other way. If somebody has an idea on how to properly fix this, please let us know or, even better, submit a PR :)

Still, this issue on react-native-maps component. I imagine it's a concurrent problem?
Do you have any idea of where is the problem?

Any progress on pinpointing the issue? Seems we haven't hear anything since April.

We still did not have time to further investigate what's going on here. Feel free to look into the issue and submit a pull request!

As mentioned earlier, the underlying issue seems to be an Android framework bug where ImageView doesn't properly restore the visibility of the underlying Drawable when returning from the transition.

I've filed an Android bug report here: https://issuetracker.google.com/issues/111293868

I've also created a (non-Fresco) sample app that outlines the issue: https://github.com/oprisnik/VisibilityPlayground

We'll investigate if there is a workaround for this issue. However, Fresco requires manual memory management and we rely on the Drawable visibility to re-request the image, so I'm not sure if there is a better fix than the workarounds described above.

I switched to fresco not being aware of this issue. I render a ton of gifs and it does a great job, but I'm also dependent on these animations for navigation. I've been able to use the above mentioned hacks, but on a return animation, the image will disappear.

Actually invalidate or View.setVisibility can not show drawable, you need call Drawable.setVisible(true, true) directly .
I add these codes in A Activity#OnCreate,it works fine:

ActivityCompat.setExitSharedElementCallback(this, new SharedElementCallback() {
            @Override
            public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
                super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
                if (FP.empty(sharedElements)) {
                    return;
                }
                for (View view : sharedElements) {
                    if (view instanceof SimpleDraweeView) {
                        ((SimpleDraweeView) view).getDrawable().setVisible(true, true);
                    }
                }
            }
        });

@oprisnik In your non-fresco sample app you set the transition to ChangeBounds. Does the bug happen if you remove this line? I guess the visibility is restored correctly when the ChangeImageTransform is in play. Is it possible to get fresco images to work with the ChangeImageTransform?

@RainFool is right. simpleDraweeView.getVisibility() is VISIBLE, but simpleDraweeView.getDrawable().isVisible() is false.

Yes, this is a bug in the Android framework where the View visibility (ImageView to be precise) is not propagated to the Drawable, so you have to manually update visibility. Please star the Android bug report here so that this gets more visibility: https://issuetracker.google.com/issues/111293868

Was this page helpful?
0 / 5 - 0 ratings