Question

I am working in Xamarin.Android. I have two activities on which I need to show the same image. On the first screen, I download it from a web URL and show it but I don't want to do the same on second screen. I want to save it to Internal Storage when it gets downloaded on the first screen and then simply retrieve it from there to show on second activity. How can I do that?

Here is my code that I am using on first activity:

protected override void OnCreate (Bundle bundle)
{
    base.OnCreate (bundle);

    this.SetContentView (Resource.Layout.Main);

    String uriString = this.GetUriString();
    WebClient web = new WebClient ();
    web.DownloadDataCompleted += new DownloadDataCompletedEventHandler(web_DownloadDataCompleted);
    web.DownloadDataAsync (new Uri(uriString));
}

void web_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
    if (e.Error != null)
    {
        RunOnUiThread(() =>
            Toast.MakeText(this, e.Error.Message, ToastLength.Short).Show());
    }
    else
    {
        Bitmap bm = BitmapFactory.DecodeByteArray(e.Result, 0, e.Result.Length);

        // THIS IS WHERE I NEED TO SAVE THE IMAGE IN INTERNAL STORAGE //

        RunOnUiThread(() =>
            {
                ProgressBar pb = this.FindViewById<ProgressBar> (Resource.Id.custLogoProgressBar);
                pb.Visibility = ViewStates.Gone;

                ImageView imgCustLogo = FindViewById<ImageView>(Resource.Id.imgCustLogo);
                imgCustLogo.SetImageBitmap(bm);
            });
    }
}

Now for saving the image, here is what I did inspired by this:

        Bitmap bm = BitmapFactory.DecodeByteArray(e.Result, 0, e.Result.Length);

        ContextWrapper cw = new ContextWrapper(this.ApplicationContext);
        File directory = cw.GetDir("imgDir", FileCreationMode.Private);
        File myPath = new File(directory, "test.png");

        FileOutputStream fos = null;
        try 
        {
            fos = new FileOutputStream(myPath);
            bm.Compress(Bitmap.CompressFormat.Png, 100, fos);
            fos.Close();
        }
        catch (Exception ex) 
        {
            System.Console.Write(ex.Message);
        }

However, the code does not compile and I get an exception where I call bm.Compress(). It says:

Error CS1503: Argument 3: cannot convert from 'Java.IO.FileOutputStream' to 'System.IO.Stream'
Was it helpful?

Solution

Okay this is how I got it working:

    Bitmap bm = BitmapFactory.DecodeByteArray(e.Result, 0, e.Result.Length);

    ContextWrapper cw = new ContextWrapper(this.ApplicationContext);
    File directory = cw.GetDir("imgDir", FileCreationMode.Private);
    File myPath = new File(directory, "test.png");

    try 
    {
        using (var os = new System.IO.FileStream(myPath.AbsolutePath, System.IO.FileMode.Create))
        {
            bm.Compress(Bitmap.CompressFormat.Png, 100, os);
        }
    }
    catch (Exception ex) 
    {
        System.Console.Write(ex.Message);
    }

OTHER TIPS

If you want to save it to internal storage and it will not be displayed in gallery

public static void SaveBitmapToInternalStorage(this Context context, Bitmap bitmap, string filename, string directory)
    {
        //can change directory as per need
        if (directory != null || directory != "") directory = "/" + directory;
        var imagesDir = context.GetExternalFilesDir(Android.OS.Environment.DirectoryPictures+ directory);
        if (!imagesDir.Exists()) imagesDir.Mkdirs();
        var jFile = new Java.IO.File(imagesDir, filename);
        var filePath = jFile.AbsoluteFile.ToString();

        System.IO.FileStream output = null;
        using (output = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
        {
            bitmap.Compress(Bitmap.CompressFormat.Jpeg, 90, output);
        }
        output.Close();            
    }

And to retrieve the saved image Note: filename and directory should be same in order to retrieve same file

public static Drawable GetDrawableFromInternalStorage(this Context context, string fileName, string directory)
    {
        //return drawable for imagename from internal storage
        if (directory != null || directory != "") directory = "/" + directory;
        var imagesDir = context.GetExternalFilesDir(Android.OS.Environment.DirectoryPictures + directory);
        if (!imagesDir.Exists()) return null;
        var jFile = new Java.IO.File(imagesDir, fileName);

        if (jFile.Exists())
        {
            var img = Drawable.CreateFromPath(jFile.ToString());
            return img;
        }
        return null;
    }

And to save image to gallery

public static bool SaveImageToGallery(this Context context, Bitmap bmp, string directory)
    {
        // First save the picture
        string storePath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + File.Separator + directory;
        File imgDir = new File(storePath);
        if (!imgDir.Exists()) imgDir.Mkdir();
        string fileName = System.DateTime.Today.ToLongDateString() + ".jpg";
        File file = new File(imgDir, fileName);
        try
        {
            var uri = Android.Net.Uri.FromFile(file);
            var os = context.ContentResolver.OpenOutputStream(uri);
            //Compress and save pictures by io stream
            bool isSuccess = bmp.Compress(Bitmap.CompressFormat.Jpeg, 60, os);
            os.Flush();
            os.Close();

            //Update the database by sending broadcast notifications after saving pictures                
            context.SendBroadcast(new Intent(Intent.ActionMediaScannerScanFile, uri));
            return isSuccess;
        }
        catch (IOException e) { }
        return false;
    }

If you want to create unique file name to save to gallery then

File file = File.CreateTempFile(
                "Img_",  /* prefix */
                ".jpg",         /* suffix */
                imgDir    /* directory */);

The Bitmap's compress method takes an object of OutputStream as the third parameter and what you are passing is a FileOutputStream (which drives from OutputStream). You could try passing it an object of OutputStream to see if that solves the problem.

I think this is how you convert back and forth:

using (var stream = new Java.Net.URL(uriString).OpenConnection().InputStream)
{
     bitmap = await BitmapFactory.DecodeStreamAsync(stream);
}

using (var stream = new Java.Net.URL(myPath.Path).OpenConnection().OutputStream)
{
    await bitmap.CompressAsync(Bitmap.CompressFormat.Png, 80, stream);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top