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

JFDman 1 Reputation point
2021-04-29T10:12:26.45+00:00

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

Developer technologies .NET Xamarin
0 comments No comments
{count} votes

5 answers

Sort by: Most helpful
  1. JarvanZhang 23,971 Reputation points
    2021-04-29T12:59:38.133+00:00

    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://learn.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.

    1 person found this answer helpful.
    0 comments No comments

  2. JFDman 1 Reputation point
    2021-04-30T08:12:59.793+00:00

    Hi @JarvanZhang

    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

    0 comments No comments

  3. JarvanZhang 23,971 Reputation points
    2021-04-30T09:14:04.5+00:00

    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();
                    }
                }
            }
        }
    }
    
    0 comments No comments

  4. JFDman 1 Reputation point
    2021-04-30T12:00:28.65+00:00

    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?


  5. JFDman 1 Reputation point
    2021-05-07T10:22:29.237+00:00

    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


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.