locked
Trying to share photos to social media in xamarin forms android RRS feed

  • Question

  • User339054 posted

    The below code gives this error:

    System.ObjectDisposedException: Cannot access a closed Stream. occurred

    Does anyone know how I can fix it?

      using (var stream = new MemoryStream(imageData))
                    {
    
                        Bitmap bitmap;
    
                        var intent = new Intent(Intent.ActionSend);
    
                        intent.SetType("image/png");
    
                        var imageSource = ImageSource.FromStream(() => stream);
    
    
                        bitmap = await GetBitmap(imageSource);
    
                        bitmap.Compress(Bitmap.CompressFormat.Png, 100, stream);
    
                        intent.PutExtra(Intent.ExtraStream, bitmap);
    
                        var intentChooser = Intent.CreateChooser(intent, "Share via");
    
                        StartActivityForResult(intentChooser, 1);
    
                    }
    
     Task<Bitmap> GetBitmap(ImageSource image)
            {
                return GetImageFromImageSource(image, Forms.Context);
            }
            private async Task<Bitmap> GetImageFromImageSource(ImageSource imageSource, Context context)
            {
                IImageSourceHandler handler;
    
                if (imageSource is FileImageSource)
                {
                    handler = new FileImageSourceHandler();
                }
                else if (imageSource is StreamImageSource)
                {
                    handler = new StreamImagesourceHandler(); // sic
                }
                else if (imageSource is UriImageSource)
                {
                    handler = new ImageLoaderSourceHandler(); // sic
                }
                else
                {
                    throw new NotImplementedException();
                }
    
                return await handler.LoadImageAsync(imageSource, context);
            }
    
    Sunday, August 26, 2018 8:01 PM

Answers

  • User21936 posted

    @BillyMartin

    Oh sorry, did not notice that before.

    You are using Intent.ExtraStream but passing a bitmap. According to the Android docs, section titled "Send binary content," what you need to pass as the extra is a URI to the data.

    From what I can glean from the Android docs, your best bet is to share from an image file. First a Bitmap will never be a compressed PNG. Even if you create a BitMap from a PNG, a Bitmap by definition is an uncompressed image, i.e. where a compressed image will change pixel color values s that similar adjacent colors will be compressed to be the same color and the PNG will have data that essentially says "the next 10 pixels are all color value X," whereas a Bitmap provides a color value for every pixel, even if two adjacent ones are the same... that is the definition of a bitmap.

    So if you want to share a PNG, you have to make a PNG file and send that. I am not sure what null reference you are getting. I was getting one, that had to do with a thread being null, until I used the correct context for calling StartActivityForResult. So, starting from a bitmap, this is what I believe should work:

    string filePath = "";
    using (var writeStream = new MemoryStream())
    {
        bitmap.Compress(Bitmap.CompressFormat.Png, 1, writeStream);
    
        string directory = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
        Directory.CreateDirectory(directory);
        filePath = System.IO.Path.Combine(directory, "compressedImage.png");
        if (File.Exists(filePath))
            File.Delete(filePath);
        File.WriteAllBytes(filePath, writeStream.GetBuffer());
    }
    
    string uriPath = MediaStore.Images.Media.InsertImage(MainActivity.Context.ContentResolver, filePath, "Share", "");
    Android.Net.Uri uri = Android.Net.Uri.Parse(uriPath);
    
    var intent = new Intent(Intent.ActionSend);
    intent.SetType("image/png");
    intent.PutExtra(Intent.ExtraStream, uri);
    var intentChooser = Intent.CreateChooser(intent, "Share via");
    ((MainActivity)MainActivity.Context).StartActivityForResult(intentChooser, 1);
    

    And in MainActivity. I create a static Context property:

    public static Context Context { get; set; }
    

    and set it in OnCreate (as well as asking for permission to acess external storage. :

    protected override void OnCreate(Bundle bundle)
    {
        ...
        Context = this;
    
        // Get permission to access external storage
        if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.M)
        {
            if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != Permission.Granted
                     && ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
            {
                var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
                ActivityCompat.RequestPermissions(this, permissions, 1);
            }
        }
    }
    
    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Wednesday, September 5, 2018 12:03 AM

All replies

  • User21936 posted

    @BillyMartin

    First I want to say this is not my area of expertise, but I think the issue may be because you are using the same non- resizable MemoryStream for reading and writing. So going by the docs for MemoryStream, let's first look at the constructors. Assuming imageData is a byte[], then this line:

    using (var stream = new MemoryStream(imageData))
    

    is using this constructor for MemoryStream:

    public MemoryStream (byte[] buffer);
    

    which, according to the docs:

    Initializes a new non-resizable instance of the MemoryStream class based on the specified byte array.

    So when you re-use that stream variable later:

    bitmap.Compress(Bitmap.CompressFormat.Png, 100, stream);
    

    according to the docs, the stream you pass to Bitmap.Compress is the stream that will be written to, IOW it will hold the compressed bitmap. But the stream was created to be non-resizable, and your compressed bitmap should be smaller than your uncompressed bitmap that you created from stream.

    So what you might try is this:

    Bitmap bitmap;
    Bitmap compressedBitmap;
    using (var stream = new MemoryStream(imageData))
    {
        var imageSource = ImageSource.FromStream(() => stream);
        bitmap = await GetBitmap(imageSource);
    }
    using (var writeStream = new MemoryStream())
    {              
        bitmap.Compress(Bitmap.CompressFormat.Png, 100, writeStream);
        // Actually not sure how this will work with a compressed image, 
        // but do here whatever you need to do to get your image from writeStream
        var compressedImageSource = ImageSource.FromStream(() => writeStream);
        compressedBitmap = await GetBitmap(compressedImageSource); 
    }
    
    var intent = new Intent(Intent.ActionSend);
    intent.SetType("image/png");
    intent.PutExtra(Intent.ExtraStream, compressedBitmap);
    var intentChooser = Intent.CreateChooser(intent, "Share via");
    StartActivityForResult(intentChooser, 1);
    
    Thursday, August 30, 2018 1:43 AM
  • User339054 posted

    @JGoldberger , thanks for the help. I had something similar once, but I couldn't get past

    bitmap.Compress(Bitmap.CompressFormat.Png, 100, writeStream);

    It seems you are going backword and trying to put it back into an image source. I've googled the heck out of it, but I can't figure out what to do. I have a bitmap, then try to make it a ping, but then it's null from then on.

    Friday, August 31, 2018 10:23 AM
  • User21936 posted

    @BillyMartin

    I think maybe you want to create a png file and then send that for sharing? If so, this should work once you have the Bitmap:

    using (var writeStream = new MemoryStream())
    {
        bitmap.Compress(Bitmap.CompressFormat.Png, 1, writeStream);
    
        string filePath = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), "compressedImage.png");
        if (File.Exists(filePath))
            File.Delete(filePath);
        File.WriteAllBytes(filePath, writeStream.GetBuffer());
    }
    

    Now you will have your png file at filePath.

    Don't forget to get permission to write to external storage on Marshmallow and later. Put the following in MainActivity.OnCreate():

      if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.M)
      {
            if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != Permission.Granted
                     && ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
            {
                  var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
                  ActivityCompat.RequestPermissions(this, permissions, 1);
            }
      }
    
    Saturday, September 1, 2018 3:22 AM
  • User339054 posted

    @JGoldberger , thanks for the help. I think I'm getting closer, but now I'm getting a null reference exception.

    I think I need to change the PutExtra statement. What do you think?

            var intent = new Intent(Intent.ActionSend);
                    intent.SetType("image/png");
                    intent.PutExtra(Intent.ExtraStream, bitmap);
                    var intentChooser = Intent.CreateChooser(intent, "Share via");
                    StartActivityForResult(intentChooser, 1);
    
    Saturday, September 1, 2018 9:27 AM
  • User21936 posted

    @BillyMartin

    Oh sorry, did not notice that before.

    You are using Intent.ExtraStream but passing a bitmap. According to the Android docs, section titled "Send binary content," what you need to pass as the extra is a URI to the data.

    From what I can glean from the Android docs, your best bet is to share from an image file. First a Bitmap will never be a compressed PNG. Even if you create a BitMap from a PNG, a Bitmap by definition is an uncompressed image, i.e. where a compressed image will change pixel color values s that similar adjacent colors will be compressed to be the same color and the PNG will have data that essentially says "the next 10 pixels are all color value X," whereas a Bitmap provides a color value for every pixel, even if two adjacent ones are the same... that is the definition of a bitmap.

    So if you want to share a PNG, you have to make a PNG file and send that. I am not sure what null reference you are getting. I was getting one, that had to do with a thread being null, until I used the correct context for calling StartActivityForResult. So, starting from a bitmap, this is what I believe should work:

    string filePath = "";
    using (var writeStream = new MemoryStream())
    {
        bitmap.Compress(Bitmap.CompressFormat.Png, 1, writeStream);
    
        string directory = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
        Directory.CreateDirectory(directory);
        filePath = System.IO.Path.Combine(directory, "compressedImage.png");
        if (File.Exists(filePath))
            File.Delete(filePath);
        File.WriteAllBytes(filePath, writeStream.GetBuffer());
    }
    
    string uriPath = MediaStore.Images.Media.InsertImage(MainActivity.Context.ContentResolver, filePath, "Share", "");
    Android.Net.Uri uri = Android.Net.Uri.Parse(uriPath);
    
    var intent = new Intent(Intent.ActionSend);
    intent.SetType("image/png");
    intent.PutExtra(Intent.ExtraStream, uri);
    var intentChooser = Intent.CreateChooser(intent, "Share via");
    ((MainActivity)MainActivity.Context).StartActivityForResult(intentChooser, 1);
    

    And in MainActivity. I create a static Context property:

    public static Context Context { get; set; }
    

    and set it in OnCreate (as well as asking for permission to acess external storage. :

    protected override void OnCreate(Bundle bundle)
    {
        ...
        Context = this;
    
        // Get permission to access external storage
        if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.M)
        {
            if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != Permission.Granted
                     && ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
            {
                var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
                ActivityCompat.RequestPermissions(this, permissions, 1);
            }
        }
    }
    
    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Wednesday, September 5, 2018 12:03 AM
  • User339054 posted

    Worked like a champ! Thank you so much, @JGoldberger ! You'd never believe how may forums I read on it, but you solved it!

    Wednesday, September 5, 2018 3:13 PM