Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

In this tutorial, you're going to create a simple to-do app that saves data to local storage using SQLite. With this app, you'll a have simple, working to-do list that you can use for everyday tasks. Follow this walkthrough to create your first Crosslight App. At the end of this tutorial, you should have the following result:

Panel

Samples:

CrosslightToDo.zip

Follow these steps:

Table of Contents
maxLevel3
stylecircle

Anchor
preqreuisite
preqreuisite

Prerequisite

Before starting the walkthrough, it is recommended that you have followed these walkthroughs in order:

It is also recommended that you have read through these conceptual topics in order to get a better understanding:

To use this walkthrough, you will need to use at least Crosslight version 5.0.5000.625 in order to achieve the desired result.

Let's get started. 

Preparing the Project

To start off, create a new Blank template using the Crosslight Project Wizard and give the project the name: CrosslightToDo. This project will have several notable features:

  • Displaying the list of to-dos using TableView / RecyclerView.
  • Adding data with the help of DialogPresenter.
  • Completing and Deleting to-do items using swipe gesture.
  • Calling SQLite functions from the ViewModel, persisting the data in local database storage. So when you kill the app and restart the app, it will retrieve the data from the SQLite.

Installing NuGet References

Begin by installing these NuGet references:

  • Install Intersoft.Crosslight to all projects. Update them to the latest version.
  • Install Intersoft.Crosslight.Data to all projects.
  • Install Intersoft.Crosslight.Data.Sqlite to all projects.
  • Install Intersoft.AppFramework to all projects.
  • Install Intersoft.Crosslight.Android.v7 to CrosslightToDo.Android project.
  • Install Microsoft.Bcl.Async to all projects. Once you've done this, remove references to System.IO, System.Runtime and System.Threading.Tasks from all iOS and Android project references.

If you need help in installing the NuGet references, refer to the docs. Once you've finished these, you're ready to proceed.

Preparing the Models

Before we start, let's prepare the ToDo model. Create a new empty class called ToDo and put it inside CrosslightToDo.Core/Models folder. Use the following code.

Code Block
languagec#
using System;
using CrosslightToDo.Models;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Data.ComponentModel;
namespace CrosslightToDo.Core
{
    [Serializable]
    public class ToDo : ModelBase
    {
        /// <summary>
        ///     Required for ViewModel initialization.
        /// </summary>
        public ToDo()
        {
        }
        public ToDo(string taskName)
        {
            this.Text = taskName;
            this.Id = Guid.NewGuid();
        }

        private Guid _id;
        private bool _isCompleted;
        private string _text;

        [PrimaryKey]
        public Guid Id
        {
            get { return _id; }
            set
            {
                if (_id != value)
                {
                    _id = value;
                    OnPropertyChanged("Id");
                }
            }
        }

        [Column("IsCompleted")]
        public bool IsCompleted
        {
            get { return _isCompleted; }
            set
            {
                if (_isCompleted != value)
                {
                    _isCompleted = value;
                    OnPropertyChanged("IsCompleted");
                }
            }
        }

        [Column("Text")]
        public string Text
        {
            get { return _text; }
            set
            {
                if (_text != value)
                {
                    _text = value;
                    OnPropertyChanged("Text");
                }
            }
        }
    }
}

The ToDo model only contains three properties:

  • Id: Guid. This Id is required for the SQLite services when inserting new data. This property is marked as the Primary Key.
  • IsCompleted: bool. This is required to let the user know when the ToDo is completed, which will be later displayed using a strikethrough text attribute style. This property is marked with the ColumnAttribute Class to indicate the column in the SQLite table.
  • Text: string. This property is used to hold the ToDo text. This property is also marked with the ColumnAttribute Class.

The entire class is also marked with SerializableAttribute Class.

Preparing the ViewModels

Let's proceed by creating the ViewModels. Open SimpleViewModel.cs file located inside CrosslightToDo.Core/ViewModels folder and use the following code.

Code Block
languagec#
using System;
using System.Linq;
using CrosslightToDo.Core;
using Intersoft.AppFramework;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Data.SQLite;
using Intersoft.Crosslight.Mobile;
using Intersoft.Crosslight.ViewModels;
namespace CrosslightToDo.ViewModels
{
    public class SimpleViewModel : EditableListViewModelBase<ToDo>
    {
        public SimpleViewModel()
        {
            this.Title = "My To-Do List";
            IActivatorService activatorService = this.GetService<IActivatorService>();
            var factory = activatorService.CreateInstance<Func<string, ISQLiteConnection>>();
            // obtain an instance of SQLiteConnection with the specified local data path
            this.DB = factory(this.LocalStorageService.GetFilePath(this.AppSettings.LocalDatabaseName, LocalFolderKind.Data));
        }
        public AppSettings AppSettings
        {
            get { return Container.Current.Resolve<AppSettings>(); }
        }
        public ISQLiteConnection DB { get; set; }
        public ILocalStorageService LocalStorageService
        {
            get { return ServiceProvider.GetService<ILocalStorageService>(); }
        }
        protected override void ExecuteAdd(object parameter)
        {
            base.ExecuteAdd(parameter);
            this.DialogPresenter.Show<AddToDoViewModel>(new DialogOptions("Add New Item"), result =>
            {
                AddToDoViewModel viewModel = result.ViewModel as AddToDoViewModel;
                if (string.IsNullOrEmpty(viewModel.ToDoText))
                    this.ToastPresenter.Show("You haven't entered any text.");
                else
                {
                    ToDo todo = new ToDo(viewModel.ToDoText);
                    this.DB.Insert(todo);
                    //Important: call this to refresh the table view
                    this.OnDataInserted(todo);
                }
            });
        }
        protected override void ExecuteEditAction(object parameter)
        {
            EditingParameter editingParameter = parameter as EditingParameter;
            if (editingParameter != null)
            {
                string editAction = editingParameter.CustomAction;
                if (editAction == "Delete")
                {
                    this.DB.Delete(editingParameter.Item as ToDo);
                    //Important: call this to refresh the table view
                    this.OnDataRemoved(editingParameter.Item as ToDo);
                }
                else if (editAction == "Complete")
                {
                    ToDo todo = editingParameter.Item as ToDo;
                    todo.IsCompleted = true;
                    this.DB.Update(todo);
                    //Important: call this to refresh the table view
                    this.OnDataChanged(todo);
                }
                editingParameter.ShouldEndEditing = true;
            }
        }
        public override void Navigated(NavigatedParameter parameter)
        {
            base.Navigated(parameter);
            if (!this.DB.TableExists<ToDo>())
            {
                this.DB.CreateTable<ToDo>();
                this.DB.Insert(new ToDo("Buy groceries for the month"));
                this.DB.Insert(new ToDo("Walk the dog"));
                this.DB.Insert(new ToDo("Learn Crosslight"));
            }
            this.SourceItems = this.DB.Table<ToDo>().ToList().ToObservable();
        }
    }
}

Let's take a look at the code from the top.

  • This class subclasses from EditableListViewModelBase(T) Class which takes in the ToDo class.
  • In the ViewModel's constructor, we've instantiated a connection to the SQLite service and store it in a handy local property called DB.
  • In the Properties region, we have several handy properties to be used throughout the ViewModel, mostly by resolving the services through ServiceProvider.
  • In the ExecuteAdd method, we've invoked a DialogPresenter that will display the Add New Item dialog. Upon callback, we simply take the value specified in the text box.
  • The ExecuteEditAction method will handle the swipe gesture that will be applied to each item.
  • In the Navigated method, we simply check if the table is not existed, then we create one and populate several to-do items which will then be supplied to the SourceItems.

One important note that is after you've performed any data operation, whether it's insert, update, or delete, you should call the corresponding OnDataXXX method in order for the UI to be updated correctly.

Proceed by creating a new Crosslight View Model called AddToDoViewModel.cs inside CrosslightToDo.Core/ViewModels folder. Use the following code.

Code Block
languagec#
using Intersoft.Crosslight.ViewModels;
namespace CrosslightToDo.ViewModels
{
    public class AddToDoViewModel : ViewModelBase
    {
        public AddToDoViewModel()
        {
        }

        private string _toDoText;

        public string ToDoText
        {
            get { return _toDoText; }
            set
            {
                if (_toDoText != value)
                {
                    _toDoText = value;
                    OnPropertyChanged("ToDoText");
                }
            }
        }
    }
}

This ViewModel contains nothing except for the ToDoText property which will bound to the text field in the Dialog Presenter. Now that you have the ViewModels ready, let's proceed by performing several 

Preparing the BindingProviders

Once you've prepared the ViewModels, let's prepare the BindingProvider instances. Open up SimpleBindingProvider.cs located inside CrosslightToDo.Core/BindingProviders folder and change it to the following.

Code Block
languagec#
using CrosslightToDo.Core;
using Intersoft.Crosslight;
namespace CrosslightToDo
{
    public class SimpleBindingProvider : BindingProvider
    {
        public SimpleBindingProvider()
        {
            ItemBindingDescription itemBinding = new ItemBindingDescription()
            {
                DisplayMemberPath = "Text",
            };
            itemBinding.AddBinding("TextLabel", BindableProperties.StyleAttributesProperty, new BindingDescription("IsCompleted", BindingMode.TwoWay)
            {
                Converter = new TextLabelStyleConverter()
            });
            this.AddBinding("TableView", BindableProperties.ItemsSourceProperty, "Items");
            this.AddBinding("TableView", BindableProperties.ItemTemplateBindingProperty, itemBinding, true);
            this.AddBinding("TableView", BindableProperties.IsEditingProperty, "IsEditing", BindingMode.TwoWay);
            this.AddBinding("TableView", BindableProperties.EditActionCommandProperty, "EditActionCommand", BindingMode.TwoWay);
            this.AddBinding("AddButton", BindableProperties.CommandProperty, "AddCommand");
            this.AddBinding("DeleteButton", BindableProperties.CommandProperty, "DeleteCommand");
        }
    }
}

The BindingProvider above provides the required bindings for the main screen to work properly. For example, we've simply bound the DisplayMemberPath to the Text property. We've also added an additional TextLabelStyleConverter which will change the item's label appearance to strikethrough whenever a ToDo is completed. We then bind the table view to the built-in property IsEditing in the ViewModel, which will allow swipe gesture actions. The EditActionCommand property allows for the real implementation when editing event occurs. Then we simply bound two buttons, the AddButton as well as the DeleteButton.

Create a new Crosslight Binding Provider and call it AddToDoBindingProvider.cs. Use the following code.

Code Block
languagec#
using Intersoft.Crosslight;
namespace CrosslightToDo
{
    public class AddToDoBindingProvider : BindingProvider
    {
        #region Constructors
        public AddToDoBindingProvider()
        {
            this.AddBinding("TxtToDo", BindableProperties.TextProperty, new BindingDescription("ToDoText", BindingMode.TwoWay, UpdateSourceTrigger.PropertyChanged));
        }
        #endregion
    }
}

Here, simply bind the text box with ID of TxtToDo to the ToDoText property in the ViewModel, which will be triggered automatically upon PropertyChanged.

Additional Core Tasks

Remove Container.cs from CrosslightToDo.Core/Infrastructure folder. Once you've done that, open up AppService.cs inside CrosslightToDo.Core/Infrastructure folder. Change the constructor to the following code.

Code Block
languagec#
public CrosslightAppAppService(IApplicationContext context)
    : base(context)
{
    AppSettings appSettings = new AppSettings();
    appSettings.LocalDatabaseName = "todo.db3";
    appSettings.LocalDatabaseLocation = LocalFolderKind.Data;
    Container.Current.RegisterInstance(appSettings);
}

This will configure the SQLite database name as well as where it should be stored. It is registered to the AppFramework's Container instance to be resolved in the SimpleViewModel.cs, as shown in the previous code. If you don't remove the Container.cs from the CrosslightToDo.Core/Infrastructure folder, the Container instance may be confused with the one specified here. Let's use the Container instance from the AppFramework instead.

Create a new folder called Converters inside CrosslightToDo.Core project. Then add a new empty class called TextLabelStyleConverter.cs and add the following code.

Code Block
languagec#
using System;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Drawing;
namespace CrosslightToDo.Core
{
    public class TextLabelStyleConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is bool)
            {
                bool isCompleted = (bool) value;
                if (isCompleted)
                {
                    StyleAttributes style = new StyleAttributes();
                    style.Strikethrough = true;
                    style.ForegroundColor = Colors.LightGray;
                    return style;
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

This converter will return the appropriate label style depending on the value passed to it. In this case, it's the IsCompleted property. If it's true, it will return the strikethrough style and the color of the label will be changed to light gray.

Preparing the iOS Project

Once you've got everything ready. Let's modify the MainViewController.cs inside CrosslightToDo.iOS/ViewControllers folder and use the following code.

Code Block
languagec#
using CrosslightToDo.ViewModels;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
using UIKit;
namespace CrosslightToDo.iOS
{
    [ImportBinding(typeof(SimpleBindingProvider))]
    public partial class MainViewController : UITableViewController<SimpleViewModel>
    {
        public override TableViewCellStyle CellStyle
        {
            get { return TableViewCellStyle.Default; }
        }
        public override EditingOptions EditingOptions
        {
            get { return EditingOptions.AllowEditing; }
        }
        protected override string EmptyCellText
        {
            get { return "No to-do items."; }
        }
        public override TableViewInteraction InteractionMode
        {
            get { return TableViewInteraction.None; }
        }

        protected override void InitializeView()
        {
            base.InitializeView();
            var addButton = new UIBarButtonItem(UIBarButtonSystemItem.Add);
            this.NavigationItem.SetRightBarButtonItem(addButton, false);
            this.RegisterViewIdentifier("AddButton", addButton);
            this.EditActions.Add(new UIEditAction("Delete", true));
            this.EditActions.Add(new UIEditAction("Complete", UIColor.DarkGray));
        }
    }
}

Here, we simply configured the UITableViewController to use the Default CellStyle, EditingOptions set to AllowEditing, specified the EmptyCellText, and don't allow any interactions for the TableView. In the InitializeView method, we initialized a UIBarButtonItem for the Add button then register the button at runtime. The swipe gestures are also defined by adding items to EditActions. Next, create a new Crosslight View Controller for iPhone called AddToDoViewController. Use the following code.

Code Block
languagec#
using System;
using CrosslightToDo.ViewModels;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
namespace CrosslightToDo.iOS
{
    [Storyboard("MainStoryboard")]
    [ImportBinding(typeof(AddToDoBindingProvider))]
    public partial class AddToDoViewController : UIViewController<AddToDoViewModel>
    {
        #region Constructors
        public AddToDoViewController(IntPtr intPtr)
            : base(intPtr)
        {
        }
        #endregion
    }
}

This is basically an empty ViewController which loads a layout from the MainStoryboard.storyboard file located inside CrosslightToDo.iOS/Views folder. Next, open up the said storyboard.

Simply drag a UITextField onto the canvas, and give it an outlet called TxtToDo. This corresponds to the AddToDoBindingProvider defined earlier. Before running the project, there's one final step to do. Open up the AppInitializer.cs inside CrosslightToDo.iOS/Infrastructure folder.

Code Block
languagec#
using CrosslightToDo.Infrastructure;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
using PushNotification = Intersoft.Crosslight.Services.PushNotification.iOS;
using Sqlite = Intersoft.Crosslight.Data.SQLite;
namespace CrosslightToDo.iOS.Infrastructure
{
    /// <summary>
    ///     iOS's application initializer.
    /// </summary>
    /// <seealso cref="Intersoft.Crosslight.IApplicationInitializer" />
    public sealed class AppInitializer : IApplicationInitializer
    {
        #region Implementation of IApplicationInitializer
        /// <summary>
        ///     Gets the application service based on the current context.
        /// </summary>
        /// <param name="context">The context of the application.</param>
        /// <returns>
        ///     The application service.
        /// </returns>
        public IApplicationService GetApplicationService(IApplicationContext context)
        {
            return new CrosslightAppAppService(context);
        }
        /// <summary>
        ///     Initializes and prepares the application for launch.
        /// </summary>
        /// <param name="appHost">The application host.</param>
        public void InitializeApplication(IApplicationHost appHost)
        {
        }
        /// <summary>
        ///     Initializes the components required for the application to run properly.
        /// </summary>
        /// <param name="appHost">The application host.</param>
        public void InitializeComponents(IApplicationHost appHost)
        {
        }
        /// <summary>
        ///     Initializes the services for the application to start properly.
        /// </summary>
        /// <param name="appHost">The application host.</param>
        public void InitializeServices(IApplicationHost appHost)
        {
            UIApplicationDelegate.PreserveAssembly((typeof(Sqlite.ServiceInitializer).Assembly));
        }
        #endregion
    }
}

In the InitializeServices method, we invoked the PreserveAssembly method so that it will not be stripped out by the Xamarin.iOS linker during the build process.

Try to run the project and you should get the following result.

Preparing the Android Project

Now that you have the iOS project ready, next thing to do is to prepare the Android project. First, open up SimpleFragment.cs inside CrosslightToDo.Android/Fragments folder and use the following code.

Code Block
languagec#
using System;
using Android.Graphics;
using Android.Runtime;
using CrosslightToDo.ViewModels;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Android;
using Intersoft.Crosslight.Android.v7;
namespace CrosslightToDo.Android.Fragments
{
    /// <summary>
    ///     The main Fragment contained inside SimpleActivity.
    ///     This class is decorated with the ImportBindingAttribute that indicates
    ///     the binding provider to be used with this Activity.
    /// </summary>
    /// <seealso cref="Intersoft.Crosslight.Android.v7.Fragment{CrosslightToDo.ViewModels.SimpleViewModel}" />
    [ImportBinding(typeof(SimpleBindingProvider))]
    public class SimpleFragment : RecyclerViewFragment<SimpleViewModel>
    {
        #region Constructors
        /// <summary>
        ///     Initializes a new instance of the <see cref="SimpleFragment" /> class.
        /// </summary>
        public SimpleFragment()
        {
        }
        /// <summary>
        ///     Initializes a new instance of the <see cref="SimpleFragment" /> class.
        /// </summary>
        /// <param name="javaReference">The java reference.</param>
        /// <param name="transfer">The transfer.</param>
        public SimpleFragment(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }
        #endregion
        #region Methods
        /// <summary>
        ///     Initializes this instance.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
            this.IconId = Resource.Drawable.ic_toolbar;
            this.CellStyle = CellStyle.Default;
            this.InteractionMode = ListViewInteraction.None;
            this.EditingOptions = EditingOptions.AllowEditing;
            this.AddBarItem(new BarItem("AddButton", CommandItemType.Add));
            this.EditActions.Add(new Intersoft.Crosslight.Android.v7.ComponentModels.EditAction("Complete", Color.DarkSlateGray));
            this.EditActions.Add(new Intersoft.Crosslight.Android.v7.ComponentModels.EditAction("Delete", Color.Red));
        }
        #endregion
    }
}

The Fragment above inherits from RecyclerViewFragment(TViewModel) Class introduced with Crosslight Android Material. It requires two default constructors as shown above to work properly. In the Initialize method, we simply provided the necessary configurations, such as IconId, CelStyle, InteractionMode, EditingOptions. Then we add a BarItem for the AddButton, and provided the swipe gesture actions by adding more EditActions.

Next, create a new Fragment for the AddToDoFragment. In the same folder, create a new Crosslight Material Fragment and name it AddToDoFragment. Use the following code.

Code Block
languagec#
using System;
using Android.Runtime;
using CrosslightToDo.ViewModels;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Android.v7;
namespace CrosslightToDo.Android
{
    [ImportBinding(typeof(AddToDoBindingProvider))]
    public class AddToDoFragment : Fragment<AddToDoViewModel>
    {
        #region Constructors
        public AddToDoFragment()
        {
        }
        public AddToDoFragment(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }
        #endregion
        #region Properties
        protected override int ContentLayoutId
        {
            get { return Resource.Layout.add_todo_layout; }
        }
        #endregion
    }
}

This class inherits from standard Fragment, then two required constructors are added, then we've overridden the ContentLayoutId to use add_todo_layout. Create this file under CrosslightToDo.Android/Resources/layout folder. Then use the following code for the layout. This will be used for the content of the Dialog Presenter.

Code Block
languagexml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="10dp"
    android:paddingTop="10dp"
    android:paddingRight="10dp">
    <EditText
        android:id="@+id/TxtToDo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Before running the project, open up the AppInitializer.cs inside CrosslightToDo.Android/Infrastructure folder and use the following code.

Code Block
languagec#
using CrosslightToDo.Infrastructure;
using Intersoft.Crosslight;
using Sqlite = Intersoft.Crosslight.Data.SQLite;
namespace CrosslightToDo.Android.Infrastructure
{
    /// <summary>
    ///     Android Application Initializer class.
    /// </summary>
    /// <seealso cref="Intersoft.Crosslight.IApplicationInitializer" />
    public sealed class AppInitializer : IApplicationInitializer
    {
        #region Implementation of IApplicationInitializer
        /// <summary>
        ///     Gets the application service based on the current context.
        /// </summary>
        /// <param name="context">The context of the application.</param>
        /// <returns>
        ///     The application service.
        /// </returns>
        public IApplicationService GetApplicationService(IApplicationContext context)
        {
            return new CrosslightAppAppService(context);
        }
        /// <summary>
        ///     Initializes and prepares the application for launch.
        /// </summary>
        /// <param name="appHost">The application host.</param>
        public void InitializeApplication(IApplicationHost appHost)
        {
        }
        /// <summary>
        ///     Initializes the components required for the application to run properly.
        /// </summary>
        /// <param name="appHost">The application host.</param>
        public void InitializeComponents(IApplicationHost appHost)
        {
        }
        /// <summary>
        ///     Initializes the services for the application to start properly.
        /// </summary>
        /// <param name="appHost">The application host.</param>
        public void InitializeServices(IApplicationHost appHost)
        {
            AndroidApp.PreserveAssembly((typeof(Sqlite.ServiceInitializer).Assembly));
        }
        #endregion
    }
}

Similar to iOS, this is needed for the Xamarin.Android linker to retain the SQLite service initializer and not strip it out during the build process.

Run the project and you should see the following result.

Conclusion

Congratulations! You've just finished a simple to-do app using Crosslight that saves data to SQLite. In the next tutorial, we're going to continue from this project and enable data synchronization. You can find the source code to this tutorial here: CrosslightToDo.

Sample

You can also find the resulting sample here: CrosslightToDo.zip. Simply open this sample with Xamarin Studio or Visual Studio, restore the NuGet packages and run the project. Should you encounter any problems during the restoration process, consult this documentation.

 

Related Topics

Content by Label
spacescrosslight
reversetrue
showLabelsfalse
max5
sortmodified
labelsgetting-started-with-crosslight -create-a-to-do-app-with-sqlite-storage
showSpacefalse
typepage