Xamarin.Tips – Super Simple Sqlite

Thinking about locally storing data and entity structures can be intimidating. You might have a complex data structure in your backend server structure and are trying to match that type of data in your mobile apps. Perhaps you’ve just never done it before and need a solution quickly. You might be over thinking it!

Let’s break it down into some straightforward steps.

  1. Create a Xamarin PCL or Shared project. It doesn’t matter if it’s Xamarin.Forms or native.
  2. Install this nuget package in your PCL or Shared project: https://www.nuget.org/packages/sqlite-net-pcl/
  3. Install the same nuget package in your Android, iOS, UWP, and any other project.
  4. Create your model in your PCL or Shared project. In this case, we will use a basic example model:
    public class Profile
    {
        public string Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Handle { get; set; }
        public DateTime CreatedDate { get; set; }
    }
    
  5. Create a DbContext class in your PCL or Shared project:
    public class DbContext
    {
        public static string LocalFilePath; // Set this before creating from platform project
        public SQLiteAsyncConnection Database { get; }
        /// <summary>
        /// Initialized a new DbContext
        /// </summary>
        public DbContext()
        {
            Database = new SQLiteAsyncConnection(LocalFilePath + "localdb.db3");
        }
    
        /// <summary>
        /// Creates a table for a given type in sql lite
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public async Task<CreateTablesResult> CreateTableAsync<T>() where T : new()
        {
        return await Database.CreateTableAsync<T>();
        }
    
        /// <summary>
        /// Gets a table by it's type from the db.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public AsyncTableQuery<T> Set<T>() where T : new()
        {
            return Database.Table<T>();
        }
    }
    
  6. Create a GenericRepository Class:
    public class GenericSqliteRepository<T> : IGenericRepository<T> where T : new()
    {
        protected readonly DbContext _context;
        public GenericSqliteRepository(DbContext context)
        {
            _context = context;
        }
        public virtual async Task InitializeAsync()
        {
            await _context.CreateTableAsync<T>();
        }
        public virtual async Task AddAsync(T entity)
        {
            await _context.Database.InsertOrReplaceAsync(entity);
        }
        public virtual async Task AddRangeAsync(IEnumerable<T> entities)
        {
            await _context.Database.InsertAllAsync(entities);
        }
    
        public virtual async Task<T> FindAsync(Expression<Func<T, bool>> predicate)
        {
            return await _context.Set<T>().Where(predicate).FirstOrDefaultAsync();
        }
    
        public virtual async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> predicate)
        {
            return await _context.Set<T>().Where(predicate).ToListAsync();
        }
    
        public virtual async Task<IEnumerable<T>> GetAsync(int skip, int take)
        {
            return await _context.Set<T>().Skip(skip).Take(take).ToListAsync();
        }
    
        public virtual async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> predicate, int skip, int take)
        {
            return await _context.Set<T>().Where(predicate).Skip(skip).Take(take).ToListAsync();
        }
    
        public virtual async Task RemoveAsync(T entity)
        {
            await _context.Database.DeleteAsync(entity);
        }
    
        public virtual async Task UpdateAsync(T entity)
        {
            await _context.Database.UpdateAsync(entity);
        }
    }
    
  7. Create a ProfileRepository Class:
    public class ProfileRepository : GenericSqliteRepository<Post>
    {
        public ProfileRepository(DbContext context) : base(context)
        {
        }
    }
    
  8. Set your file path in your platform code:
    1. Android
      DbContext.LocalFilePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
      
    2. iOS
      var docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
      string libFolder = Path.Combine(docFolder, "..", "Library", "Databases");
      
      if (!Directory.Exists(libFolder))
      {
          Directory.CreateDirectory(libFolder);
      }
      DbContext.LocalFilePath = libFolder;
      
  9. Use your repository in your shared code or platform code or wherever you want.
    var repo = new ProfileRepository(new DbContext());
    await repo.InitializeAsync();
    ...
    await repo.AddAsync(new Profile {...});
    ...
    var profiles = await repo.GetAsync(0, 10);
    ...
    var profile = await repo.FindAsync(p => p.Id == "foo");
    ...
    
  10. Start storing your things!

This is obviously a simple situation that doesn’t cover all needs, but it’s a place to start for complex data models. Build repositories for each of your types. Control your queries that are very specific in those model-specific repositories.

Look out for a follow up post about some patterns and tips to use for complex and large data sets!

Advertisements

Creating Google Hangouts Style Images in Xamarin iOS

If you haven’t seen it yet, there is this nifty style of thumbnail, icon, image, etc that Google does in their Hangouts app along with some of their others.

hangouts
Google Hangouts

Notice the second image and the last one where they take images from members of your hangout and lay  them together in an orderly fashion based on the number of people you have.

Here is the logic:

  1. One other person – Show the other user’s image
  2. Two other people – Show both other user’s images diagonally from each other
  3. Three other people – Show the three images in a triangle or pyramid shape
  4. Four or more people – Show 4 images in a square

This logic is straightforward with platforms like Windows and Android where you can create the layouts for each one and switch which one is visible based on the number of members. In this case, however, we are going to explore my favorite way to do it in iOS while still using the standard UITableViewCell.

This means rather than using multiple UIImageViews to display each one, we need to create the image in ONE UIImageView. Here is a simple method I use to do something like this:

  private UIImage GetHangoutImage(HangoutVM hangout, UIView container)
        {
            var members = hangout.Members
                    .Where(im => CurrentUser.FullName != im.Name)
                    .Where(im => !string.IsNullOrEmpty(im.ImageUrl))
                    .Take(4)
                    .ToList();
            if (!string.IsNullOrEmpty(hangout.ImageUrl))
            {
                //if the hangout has an image, use that url
                return Extensions.UIImageFromUrl(hangout.ImageUrl);
            }
            else if (members.Count == 0)
            {
                //if no other members, show self
                return Extensions.GetRoundImage(Extensions.UIImageFromUrl(CurrentUser.ImageUrl));
            }
            else if (members.Count == 1)
            {
                //show the one other member's image
                return Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[0].ImageUrl));
            }
            else if (members.Count == 2)
            {
                //if 2 other members, show both their images diagonally
                UIGraphics.BeginImageContext(new CoreGraphics.CGSize(container.Frame.Height, container.Frame.Height));
                UIImage image1;
                UIImage image2;
                UIImage combinedImage;
                image1 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[0].ImageUrl));
                image2 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[1].ImageUrl));

                image1.Draw(new RectangleF(22, 0, 22, 22));
                image2.Draw(new RectangleF(0, 22, 22, 22));
                combinedImage = UIGraphics.GetImageFromCurrentImageContext();
                UIGraphics.EndImageContext();
                return combinedImage;
            }
            else if (members.Count == 3)
            {
                //if 3 other members, show all three images in a triangle
                UIGraphics.BeginImageContext(new CoreGraphics.CGSize(container.Frame.Height, container.Frame.Height));

                UIImage image1;
                UIImage image2;
                UIImage image3;
                UIImage combinedImage;
                image1 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[0].ImageUrl));
                image2 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[1].ImageUrl));
                image3 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[2].ImageUrl));
                image1.Draw(new RectangleF(0, 0, 22, 22));
                image2.Draw(new RectangleF(22, 0, 22, 22));
                image3.Draw(new RectangleF(11, 22, 22, 22));
                combinedImage = UIGraphics.GetImageFromCurrentImageContext();

                UIGraphics.EndImageContext();
                return combinedImage;
            }
            else
            {
                //if 4 or more show first 4 in square
                UIGraphics.BeginImageContext(new CoreGraphics.CGSize(container.Frame.Height, container.Frame.Height));

                UIImage image1;
                UIImage image2;
                UIImage image3;
                UIImage image4;
                UIImage combinedImage;
                image1 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[0].ImageUrl));
                image2 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[1].ImageUrl));
                image3 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[2].ImageUrl));
                image4 = Extensions.GetRoundImage(Extensions.UIImageFromUrl(members[3].ImageUrl));

                image1.Draw(new RectangleF(0, 0, 22, 22));
                image2.Draw(new RectangleF(22, 0, 22, 22));
                image3.Draw(new RectangleF(0, 22, 22, 22));
                image4.Draw(new RectangleF(22, 22, 22, 22));

                combinedImage = UIGraphics.GetImageFromCurrentImageContext();
                UIGraphics.EndImageContext();
                return combinedImage;
            }
        }

In this situation I’m using some hard coded values for the sake of readability. The height of my UITableViewCell is 44 (hence seeing the 22’s everywhere for splitting it in half).

Also, I use a static extensions class to get images from a url and also to create round images. I talked about these in previous blog posts here:

  1. Creating Round Images in Xamarin.iOS
  2. Creating a UIImage from a URL

So now that we have our means of getting the image, we can create it in our GetCell override method in our TableViewSource class we are using to populate our UITableView:

  public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            var cellIdentifier = _hangouts[indexPath.Row].ID.ToString();
            var currentHangout = _hangouts[indexPath.Row];

            UITableViewCell cell = tableView.DequeueReusableCell(cellIdentifier);

            //// if there are no cells to reuse, create a new one
            if (cell == null)
                cell = new InstanceTableViewCell(currentInstance);
            cell = new UITableViewCell(UITableViewCellStyle.Subtitle, cellIdentifier);
            cell.TextLabel.Text = currentHangout.DisplayName;
            cell.DetailTextLabel.Text = string.Format("{0}{1}", currentHangout.LastMessageMember, currentHangout.LastMessage);
            cell.ImageView.Image = GetHangoutImage(currentHangout, cell.ImageView); // Get the Hangout Style Image
            return cell;
        }

That’s really as far as it goes! Now using that, you’ll get something looking like this:
instancesscreenshotioscropped

The lack of spacing may be misleading, but you’ll notice the first cell has 2 other members, the second has one, the third has 4+, the 4th has 3, etc.

Feel free to comment if you have any questions or want more advice on how to make something like this more appealing!