Save Bitmap on Device in Xamarin Android

Alessio Camaroto 81 Reputation points
2021-06-02T21:20:50.567+00:00

I'm trying to save a generated bitmap on my device. The app is being developed in Xamarin.Android so the code is C#. If I run my app on the emulator (Android 10) it works but If I run it, on my device (Android 11) or emulator (Android 11), bitmap saved it is empty (bitmap file exists but it is 0kb).

Bitmap bitmap;

var imageView = FindViewById<ImageView>(Resource.Id.generatedImage);
Android.Graphics.Drawables.BitmapDrawable bitmapDrawable = ((Android.Graphics.Drawables.BitmapDrawable)imageView.Drawable);
if (bitmapDrawable != null)
{
   bitmap = bitmapDrawable.Bitmap;
   Java.IO.File path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads + Java.IO.File.Separator + $"{filename}.png");
   using (System.IO.FileStream os = new System.IO.FileStream(path.AbsolutePath, System.IO.FileMode.Create))
   {
       bitmap.Compress(Bitmap.CompressFormat.Png, 100, os);
       os.Close();
   }
}

I put in my AndroidManifest.xml:

<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

and

android:requestLegacyExternalStorage="true"

What's wrong ?

Thanks
Alessio

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

Accepted answer
  1. Anonymous
    2021-06-04T00:37:23.187+00:00

    Hello,​

    Welcome to our Microsoft Q&A platform!

    ExternalStorageDirectory returns the root path of your external storage and will be deprecated in API 29:
    https://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()
    If you want to store your files into your own application's external storage you can try:

       string testPath= Android.App.Application.Context.GetExternalFilesDir("").AbsolutePath + $"{filename}.png";  
    

    102168-image.png

    Here is my edited code.

       ImageView imageView = FindViewById<ImageView>(Resource.Id.imageView1);  
         
                   //var imageView = FindViewById<ImageView>(Resource.Id.generatedImage);  
         
                   string filename = "test";  
                   Android.Graphics.Drawables.BitmapDrawable bitmapDrawable = ((Android.Graphics.Drawables.BitmapDrawable)imageView.Drawable);  
                   if (bitmapDrawable != null)  
                   {  
                       bitmap = bitmapDrawable.Bitmap;  
                        
                      string testPath= Android.App.Application.Context.GetExternalFilesDir("").AbsolutePath + $"{filename}.png";   
                       //       Java.IO.File path = ApplicationContext.GetExternalFilesDir(Android.OS.Environment.DirectoryDownloads + Java.IO.File.Separator + $"{filename}.png");;  
                       using (System.IO.FileStream os = new System.IO.FileStream(testPath, System.IO.FileMode.Create))  
                       {  
                           bitmap.Compress(Bitmap.CompressFormat.Png, 100, os);  
                           os.Close();  
                       }  
                   }  
    

    ================================
    Update===============================

    I notice you used Android.Support.V4.App.ActivityCompat.RequestPermissions to request permission. It is not work in the Android11, please uninstall your application in the android emulator, then change it to AndroidX.AppCompat.App.ActivityCompat.RequestPermissions(this, permissions, 1);. And do not call PermissionCheck method in Oncreate method,I used your code, then run it in my emulator it worked.

    First of all, please create a Navigation drawer app temple like following screenshot,

    103635-image.png

    Then, Here is SplashActivity.cs

       using Android.App;  
       using Android.Content;  
       using Android.OS;  
       using Android.Runtime;  
       using Android.Views;  
       using Android.Widget;  
       using System;  
       using System.Collections.Generic;  
       using System.IO;  
       using System.Linq;  
       using System.Text;  
       using System.Threading.Tasks;  
         
       namespace App104  
       {  
           [Activity(Label = "SplashActivity", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]  
           public class SplashActivity : Activity  
           {  
               protected override void OnCreate(Bundle savedInstanceState)  
               {  
                   base.OnCreate(savedInstanceState);  
                   Window.AddFlags(WindowManagerFlags.Fullscreen);  
                   Window.ClearFlags(WindowManagerFlags.Fullscreen);  
                   Xamarin.Essentials.Platform.Init(this, savedInstanceState);  
                   // Create your application here  
                   LoadLayout();  
               }  
               private void LoadLayout()  
               {  
                   Window.AddFlags(WindowManagerFlags.Fullscreen);  
                   Window.ClearFlags(WindowManagerFlags.Fullscreen);  
         
                   var color = Resources.GetColor(Resource.Color.colorBackground, this.ApplicationContext.Theme);  
                   Window.SetNavigationBarColor(color);  
               }  
         
               protected override void OnResume()  
               {  
                   base.OnResume();  
                   SimulateStartup();  
               }  
         
               async void SimulateStartup()  
               {  
                   await Task.Delay(1000);  
                   //  string testPath =   
                   var pathToNewFolder = Android.App.Application.Context.GetExternalFilesDir("").AbsolutePath;  
                   if (!Directory.Exists(pathToNewFolder))  
                   {  
                       Directory.CreateDirectory(pathToNewFolder);  
                   }  
         
                   Intent intent = new Intent("ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION");  
                   intent.SetClass(this, typeof(MainActivity));  
                   intent.SetType(typeof(MainActivity).ToString());  
                   intent.AddCategory("android.intent.category.DEFAULT");  
                   intent.SetData(Android.Net.Uri.Parse($"package:{ApplicationContext.PackageName}"));  
                   StartActivity(intent);  
               }  
         
               public override void OnBackPressed() { }  
           }  
         
       }  
    

    Here is MainActivity.cs

       using System;  
       using Android;  
       using Android.App;  
       using Android.Content;  
       using Android.Content.PM;  
       using Android.Graphics;  
       using Android.OS;  
       using Android.Runtime;  
       using Android.Views;  
       using Android.Widget;  
       using AndroidX.AppCompat.App;  
       using AndroidX.AppCompat.Widget;  
       using AndroidX.Core.App;  
       using AndroidX.Core.View;  
       using AndroidX.DrawerLayout.Widget;  
       using Google.Android.Material.FloatingActionButton;  
       using Google.Android.Material.Navigation;  
       using Google.Android.Material.Snackbar;  
         
       namespace App104  
       {  
           [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar")]  
           public class MainActivity : AppCompatActivity, NavigationView.IOnNavigationItemSelectedListener  
           {  
               public static Context Context { get; set; }  
               protected override void OnCreate(Bundle savedInstanceState)  
               {  
                   base.OnCreate(savedInstanceState);  
                   Xamarin.Essentials.Platform.Init(this, savedInstanceState);  
                   SetContentView(Resource.Layout.activity_main);  
                   AndroidX.AppCompat.Widget.Toolbar toolbar = FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);  
                   SetSupportActionBar(toolbar);  
                   Context = this;  
                   FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);  
                   fab.Click += FabOnClick;  
         
                   DrawerLayout drawer = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);  
                   ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, Resource.String.navigation_drawer_open, Resource.String.navigation_drawer_close);  
                   drawer.AddDrawerListener(toggle);  
                   toggle.SyncState();  
         
                   NavigationView navigationView = FindViewById<NavigationView>(Resource.Id.nav_view);  
                   navigationView.SetNavigationItemSelectedListener(this);  
                   PermissionCheck();  
               }  
         
               public override void OnBackPressed()  
               {  
                   DrawerLayout drawer = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);  
                   if(drawer.IsDrawerOpen(GravityCompat.Start))  
                   {  
                       drawer.CloseDrawer(GravityCompat.Start);  
                   }  
                   else  
                   {  
                       base.OnBackPressed();  
                   }  
               }  
         
               public override bool OnCreateOptionsMenu(IMenu menu)  
               {  
                   MenuInflater.Inflate(Resource.Menu.menu_main, menu);  
                   return true;  
               }  
         
               public override bool OnOptionsItemSelected(IMenuItem item)  
               {  
                   int id = item.ItemId;  
                   if (id == Resource.Id.action_settings)  
                   {  
                       return true;  
                   }  
         
                   return base.OnOptionsItemSelected(item);  
               }  
         
               private void FabOnClick(object sender, EventArgs eventArgs)  
               {  
                   View view = (View) sender;  
                   Snackbar.Make(view, "Replace with your own action", Snackbar.LengthLong)  
                       .SetAction("Action", (Android.Views.View.IOnClickListener)null).Show();  
               }  
         
               public bool OnNavigationItemSelected(IMenuItem item)  
               {  
                   int id = item.ItemId;  
         
                   if (id == Resource.Id.nav_camera)  
                   {  
                       // Handle the camera action  
                   }  
                   else if (id == Resource.Id.nav_gallery)  
                   {  
         
                   }  
                   else if (id == Resource.Id.nav_slideshow)  
                   {  
         
                   }  
                   else if (id == Resource.Id.nav_manage)  
                   {  
         
                   }  
                   else if (id == Resource.Id.nav_share)  
                   {  
         
                   }  
                   else if (id == Resource.Id.nav_send)  
                   {  
         
                   }  
         
                   DrawerLayout drawer = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);  
                   drawer.CloseDrawer(GravityCompat.Start);  
                   return true;  
               }  
              
               private void PermissionCheck()  
               {  
                   if (CheckSelfPermission(Manifest.Permission.ReadExternalStorage) != Permission.Granted || CheckSelfPermission(Manifest.Permission.WriteExternalStorage) != Permission.Granted)  
                   {  
                       var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };  
                       ActivityCompat.RequestPermissions(this, permissions, 1);  
                   }  
                   else  
                   {  
                       var input = FindViewById<TextView>(Resource.Id.inputNumber);  
                       Share(input.Text, Resources.GetString(Resource.String.title), Resources.GetString(Resource.String.share_msg));  
                   }  
               }  
         
         
         
               public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)  
               {  
                   switch (requestCode)  
                   {  
                       case 1:  
                           {  
                               // If request is cancelled, the result arrays are empty.   
                               if (grantResults.Length > 0 && grantResults[0] == Permission.Granted && grantResults[1] == Permission.Granted)  
                               {  
                                   // permission was granted, yay! Do the contacts-related task you need to do.   
                                   var input = FindViewById<TextView>(Resource.Id.inputNumber);  
                                   Share(input.Text, Resources.GetString(Resource.String.title), Resources.GetString(Resource.String.share_msg));  
                               }  
                               else  
                               {  
                                   // permission denied, boo! Disable the functionality that depends on this permission.   
                               }  
                               return;  
                           }  
         
                           // other 'case' lines to check for other permissions this app might request   
                   }  
               }  
         
               
         
               public void Share(string filename, string title, string content)  
               {  
                   if (string.IsNullOrEmpty(filename) || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(content)) return;  
         
                   var imageView = FindViewById<ImageView>(Resource.Id.generatedImage);  
                   Android.Graphics.Drawables.BitmapDrawable bitmapDrawable = ((Android.Graphics.Drawables.BitmapDrawable)imageView.Drawable);  
                   if (bitmapDrawable != null)  
                   {  
                       Bitmap bitmap = bitmapDrawable.Bitmap;  
                       string testPath = Android.App.Application.Context.GetExternalFilesDir("").AbsolutePath + Java.IO.File.Separator + $"{filename}.png";  
                       using (System.IO.FileStream os = new System.IO.FileStream(testPath, System.IO.FileMode.Create))  
                       {  
                           bitmap.Compress(Bitmap.CompressFormat.Png, 100, os);  
                           os.Close();  
                       }  
         
                       var sharingIntent = new Intent();  
                       sharingIntent.SetAction(Intent.ActionSend);  
                       sharingIntent.SetType("image/*");  
                       sharingIntent.PutExtra(Intent.ExtraSubject, Resources.GetString(Resource.String.contact_subject));  
                       sharingIntent.PutExtra(Intent.ExtraText, content);  
                       sharingIntent.PutExtra(Intent.ExtraStream, testPath);  
                       StartActivity(Intent.CreateChooser(sharingIntent, title));  
                   }  
               }  
         
               private void NavigationUrl(string url)  
               {  
                   var uri = Android.Net.Uri.Parse(url);  
                   var intent = new Intent(Intent.ActionView, uri);  
                   StartActivity(intent);  
               }  
           }  
         }  
    

    Then open the content_main.xml, add Imageview and textview like following xml

       <TextView  
               android:layout_width="wrap_content"  
               android:layout_height="wrap_content"  
               android:layout_centerInParent="true"  
               android:id="@+id/inputNumber"  
               android:text="Hello World!" />  
           <ImageView  
               android:layout_width="50dp"  
               android:layout_height="50dp"  
               android:id="@+id/generatedImage"  
               android:src="@drawable/icon1"/>  
    

    ==============
    Update================

    Please change the Bitmap.Config.Alpha8 to Bitmap.Config.Argb8888 in finalImage = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888); line, becuase we store colorful images, Bitmap.Config.Alpha8 just could install white or black image. for more details, you can refer to this thread.

    Best Regards,

    Leon Lu


    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.

1 additional answer

Sort by: Most helpful
  1. Alessio Camaroto 81 Reputation points
    2021-06-21T21:00:09.557+00:00

    I find where is the problem but I don't know how to solve it. If I try to save a bitmap loaded from resources your code works but if I try to save a bitmap generated at run-time it doesn't work.

        public static Bitmap MergeImages(List<Bitmap> files)
        {
            if (files == null || files.Count == 0) return null;
    
            Canvas canvas = null;
            Rect baseRect = null;
            Rect frontRect = null;
            Bitmap finalImage = null;
    
            try
            {
                int width = files[0].Width;
                int height = files[0].Height;
    
                finalImage = Bitmap.CreateBitmap(width, height, Bitmap.Config.Alpha8);
    
                canvas = new Canvas(finalImage);
                frontRect = new Rect(0, 0, width, height);
                baseRect = new Rect(0, 0, width, height);
    
                foreach (var image in files)
                {
                    canvas.DrawBitmap(image, frontRect, baseRect, null);
                }
                return finalImage;
            }
            catch (Exception)
            {
                canvas?.Dispose();
                baseRect?.Dispose();
                frontRect?.Dispose();
                finalImage?.Dispose();
                return null;
            }
        }
    

    This is my code to merge a list of bitmap in a single bitmap (I display generated bitmap in my view). It seems it works because I can display it but if I try to save it I obtain 0Kb file size.

    Thanks
    Alessio


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.