question

JFDman-2374 avatar image
0 Votes"
JFDman-2374 asked JarvanZhang-MSFT commented

Issue on loading images from internal storage into webview with target version Android 11

Hi everyone,

currently i'm preparing my app for the target version Android 11.
I already moved all app related files to the internal storage of the app.

The issue ist that images i'm loading from internal storage do not appear in the WebView.
It works perfectly with target version Android 10.

Here is my code:


 var webView = new WebView();
    
 var htmlSource = new HtmlWebViewSource
 {
    Html = "<html><body>" + "<img src='" + filePath + "' />" + "</body>  </html>"
 };
 webView.Source = htmlSource;
    
 var webPage = new ContentPage
 {
    Title = attachment.Dateiname,
    Content = webView
 };    
    
 ...

Currently used Xamarin Forms Version: 4.8.0.1821 but also tried with Xamarin Forms Version 5.0.0.2012

The reason why i am using a WebView for images is that the user can zoom with the WebView.

Can anyone help me understanding this issue, please?
Is it a bug, or e new restriction with Android 11?

Thanks in advance.

Best regards

dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

JarvanZhang-MSFT avatar image
1 Vote"
JarvanZhang-MSFT answered

Hello,​

Welcome to our Microsoft Q&A platform!

  1. I create a basic demo to test the function, it works well on both Android 10 and 11. I tested the code with Xamarin.Forms 5. Please update the packages of both the shared project and the native project to the lastest stable version, then test again.

  2. To show a zoomable image, you cuold create a 'pin-to-zoom' container to wrap the image. Check the code:

public class PinchToZoomContainer : ContentView
{
    double currentScale = 1;
    double startScale = 1;
    double xOffset = 0;
    double yOffset = 0;

    public PinchToZoomContainer()
    {
        var pinchGesture = new PinchGestureRecognizer();
        pinchGesture.PinchUpdated += OnPinchUpdated;
        GestureRecognizers.Add(pinchGesture);
    }

    void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
    {
        if (e.Status == GestureStatus.Started)
        {
            // Store the current scale factor applied to the wrapped user interface element,
            // and zero the components for the center point of the translate transform.
            startScale = Content.Scale;
            Content.AnchorX = 0;
            Content.AnchorY = 0;
        }
        if (e.Status == GestureStatus.Running)
        {
            // Calculate the scale factor to be applied.
            currentScale += (e.Scale - 1) * startScale;
            currentScale = Math.Max(1, currentScale);

            // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
            // so get the X pixel coordinate.
            double renderedX = Content.X + xOffset;
            double deltaX = renderedX / Width;
            double deltaWidth = Width / (Content.Width * startScale);
            double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

            // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
            // so get the Y pixel coordinate.
            double renderedY = Content.Y + yOffset;
            double deltaY = renderedY / Height;
            double deltaHeight = Height / (Content.Height * startScale);
            double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

            // Calculate the transformed element pixel coordinates.
            double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
            double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);

            // Apply translation based on the change in origin.
            Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
            Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);

            // Apply scale factor
            Content.Scale = currentScale;
        }
        if (e.Status == GestureStatus.Completed)
        {
            // Store the translation delta's of the wrapped user interface element.
            xOffset = Content.TranslationX;
            yOffset = Content.TranslationY;
        }
    }
}

Display the image in the container.

<StackLayout>
    <local:PinchToZoomContainer>
        <local:PinchToZoomContainer.Content>
            <Image Source="one.png" HeightRequest="55"/>
        </local:PinchToZoomContainer.Content>
    </local:PinchToZoomContainer>
</StackLayout>

Related link:
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/gestures/pinch#creating-a-pinchtozoom-container

Best Regards,

Jarvan Zhang



If the response is helpful, please click "Accept Answer" and upvote it.

Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

JFDman-2374 avatar image
0 Votes"
JFDman-2374 answered JFDman-2374 edited

Hi @JarvanZhang-MSFT

Thank you very much for your answer.

I checked out your solution with the PinchToZoomContainer and it is working well with the ContentView as the parent class.
On the emulator i could zoom in and out, but not moving the image.
Then I tried to change the parent class to a ScrollView, so that the user has the possibility to move it while the image is zoomed.


 public class PinchToZoomContainer : ScrollView
 {
     double currentScale = 1;
     double startScale = 1;
     double xOffset = 0;
     double yOffset = 0;
    
     ...
 }


Unfortunately that didn't worked well.
Maybe its because i only could test it on a emulator?
I will check this out on monday on a real device.
Did you ever test the PinchToZoomContainer into a ScrollView?


Now i come to the WebView. Maybe i wasn't clear enough.
For the Webview i used a Path like this.

 filePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)

Its a real file from the internal local app storage of the device and not from the ressources of the project.
Is this also working for you?

Best Regards

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

JarvanZhang-MSFT avatar image
0 Votes"
JarvanZhang-MSFT answered

For the Webview i used a Path like this ...

I tested with the path in my project, it still works as excepted. How do you save the image to internal storage and get the path? Here is the related code in my sample, please check:

public partial class TestPage : ContentPage
{
    public TestPage()
    {
        InitializeComponent();

        //the image has been stored to the internal storage before
        var path = DependencyService.Get<ITestInterface>().getPath();

        var webView = new WebView();
        var htmlSource = new HtmlWebViewSource
        {
            Html = "<html><body>" + "<img src='" + path + "' />" + "</body>  </html>"
        };
        webView.Source = htmlSource;

        this.Title = "testing for webview loading";

        Content = webView;
    }
}

Store the image file and get the file path on Android platform.

[assembly: Xamarin.Forms.Dependency(typeof(TestImplementation))]
namespace TestApplication_5.Droid
{
    public class TestImplementation : ITestInterface
    {
        public string GetPath()
        {
            var backingFile = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), "test.png");
            return backingFile;
        }

        public void SaveTheFile()
        {
            Bitmap bitmap;
            Android.Graphics.Drawables.Drawable drawable = MainActivity.Instance.GetDrawable(Resource.Drawable.testImg);
            bitmap = ((BitmapDrawable)drawable).Bitmap;

            var filePath = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), "test.png");

            Java.IO.File file = new Java.IO.File(filePath);
            if (!file.Exists())
            {
                System.IO.FileStream fos = null;
                try
                {
                    fos = new System.IO.FileStream(file.AbsolutePath, System.IO.FileMode.Create);
                    bitmap.Compress(Bitmap.CompressFormat.Png, 100, fos);
                    fos.Flush();
                    fos.Close();
                }
                catch (Java.IO.IOException e)
                {
                    e.PrintStackTrace();
                }
            }
        }
    }
}
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

JFDman-2374 avatar image
0 Votes"
JFDman-2374 answered JarvanZhang-MSFT commented

I get the file path like you did but i add some more folders.

But the file is created by a webclient through a download.

 public bool DownloadAttachment(string serverPath, Document attachment)
 {
     if (FileExists(attachment))
     return true;
    
     var uriString = serverPath + "someUriString" + attachment.DocumentId.Value.ToString();
     var filePath = GetFilePath(attachment.DocumentId.Value, attachment.fileName);
     new WebClient().DownloadFile(new Uri(uriString), filePath);
     attachment.IsLoaded = true;
     return true;
 }

 public string GetFilePath(string Id, string fileName)
 {
      return Path.Combine(Path.Combine(GetNewStructureDirectoryPathAttachments(Id), fileName));
 }

 public string GetNewStructureDirectoryPathAttachments(string id)
 {
     //PATH_DIR_NEW_STRUCTURE__ATTACHMENTS is something like "Attachments"
     //id is a guid
     var rootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), PATH_DIR_NEW_STRUCTURE__ATTACHMENTS);
     return Path.Combine(rootPath, id);
 }

Before download, all directories were checked and created by another function.

Is it because of how the webclient saves the file?





· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Is it because of how the webclient saves the file?

It should not make the difference if the file is store successfully? Please make sure the file exists in the folder, try to access the file after downloading to vertify that.

0 Votes 0 ·
JFDman-2374 avatar image
0 Votes"
JFDman-2374 answered JarvanZhang-MSFT commented

Hi again,

sorry i am late with the answer but today i had time to test and did the following:

             var filePath = DependencyService.Get<AttachmentDependency>().PrepareDirectoriesAndGetFilePath(attachment);
             if (!File.Exists(filePath))
                 return;
                
             //file exists after download
             var x = File.ReadAllBytes(filePath);
             var newFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Attachments/test/test/test/test");
             Directory.CreateDirectory(newFilePath);
             var fullFilePath = Path.Combine(newFilePath + "test.png");
             File.WriteAllBytes(fullFilePath, x);

After i have read the fileand wrote it to another location , it worked.
Before i checked that the file already exists.

For me it looks like the webclient cannot store the file as Android 11 needs it....

Best regards


· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

After i have read the fileand wrote it to another location , it worked.

The issue seems to be related to the storage of the webclient. Thanks for sharing the details about the thread.

0 Votes 0 ·