FlexCalc (Android Native) (C# / Mobile / Android-Native)
Note
This demo is available in your FlexCel installation at <FlexCel Install Folder>\samples\csharp\VS2026\Mobile\Android-Native\FlexCalc and also at https://github.com/tmssoftware/TMS-FlexCel.NET-demos/tree/master/csharp/VS2026/Mobile/Android-Native/FlexCalc
Overview
This example shows how to use the FlexCel calculating engine as an interactive calculator on Android.
How It Works
Under the hood, every text field in the app is mapped to a cell in a spreadsheet (A1, A2, A3...).
Whenever a text field changes, the entire sheet is recalculated and the results are shown in the column on the right. You can use the full range of Excel functions in this demo and reference cells in the usual way (for example, you can write =A2 + 1 in the text field for A3).
As in Excel, any text that starts with = will be considered a formula.
The backing spreadsheet is saved when you exit the app and reloaded when you open it, so your formulas persist between sessions. The file is stored as config.xlsx in the app's private storage directory.
FlexCel Features Demonstrated
- XlsFile: Creating and opening Excel files programmatically
- SetCellFromString / GetCellValue: Reading and writing cell values
- TFormula: Parsing formulas entered as text
- Recalc(): Real-time recalculation of the entire workbook when any cell changes
- Save with TExcelFileFormat.v2023: Saving workbooks in modern Excel format
Implementation Details
- CalcEngine.cs: Shared business logic that wraps the FlexCel API. Pre-populates 16 rows with sample data including basic values, cell references, and math functions (SIN, COS, SQRT, etc.).
- MainActivity.cs: Entry point using a native Android
ListView. - CalcAdapter.cs: Custom
BaseAdapter<int>with aFormulaWatcher(ITextWatcher) that detects text changes and triggers recalculation of all visible cells. - App.cs: Application singleton holding a static
CalcEngineinstance shared across the app.
Files
App.cs
namespace FlexCalc
{
[Application]
public class App : Application
{
internal static CalcEngine CalcEngine { get; } = new CalcEngine();
public App(IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
: base(handle, transfer)
{
}
}
}
CalcAdapter.cs
using Android.Text;
using Android.Views;
namespace FlexCalc
{
internal class CalcAdapter : BaseAdapter<int>
{
readonly CalcEngine engine;
readonly Activity activity;
readonly List<EditText> formulaFields = new();
public CalcAdapter(Activity activity, CalcEngine engine)
{
this.activity = activity;
this.engine = engine;
}
public override int Count => Math.Max(engine.GetRowCount(), 16);
public override int this[int position] => position + 1;
public override long GetItemId(int position) => position;
public override View GetView(int position, View? convertView, ViewGroup? parent)
{
int row = position + 1;
var view = convertView ?? LayoutInflater.From(activity)!.Inflate(Resource.Layout.row_item, parent, false)!;
var label = view.FindViewById<TextView>(Resource.Id.cellLabel)!;
var formula = view.FindViewById<EditText>(Resource.Id.cellFormula)!;
var value = view.FindViewById<TextView>(Resource.Id.cellValue)!;
label.Text = $"A{row}";
value.Text = engine.GetRowValue(row);
// Remove old watcher if any
if (formula.Tag is FormulaWatcher oldWatcher)
formula.RemoveTextChangedListener(oldWatcher);
formula.Text = engine.GetRowFormulaText(row);
var watcher = new FormulaWatcher(engine, row, this);
formula.Tag = watcher;
formula.AddTextChangedListener(watcher);
// Track formula fields for refreshing
if (!formulaFields.Contains(formula))
formulaFields.Add(formula);
return view;
}
public void RefreshAllValues(ListView listView)
{
for (int i = 0; i < listView.ChildCount; i++)
{
var child = listView.GetChildAt(i);
if (child == null) continue;
int position = listView.FirstVisiblePosition + i;
int row = position + 1;
var valueView = child.FindViewById<TextView>(Resource.Id.cellValue);
if (valueView != null)
valueView.Text = engine.GetRowValue(row);
}
}
class FormulaWatcher : Java.Lang.Object, ITextWatcher
{
readonly CalcEngine engine;
readonly int row;
readonly CalcAdapter adapter;
public FormulaWatcher(CalcEngine engine, int row, CalcAdapter adapter)
{
this.engine = engine;
this.row = row;
this.adapter = adapter;
}
public void AfterTextChanged(IEditable? s)
{
engine.SetRowFormula(row, s?.ToString() ?? "");
var listView = adapter.activity.FindViewById<ListView>(Resource.Id.grid);
if (listView != null)
adapter.RefreshAllValues(listView);
}
public void BeforeTextChanged(Java.Lang.ICharSequence? s, int start, int count, int after) { }
public void OnTextChanged(Java.Lang.ICharSequence? s, int start, int before, int count) { }
}
}
}
CalcEngine.cs
using FlexCel.Core;
using FlexCel.XlsAdapter;
namespace FlexCalc
{
internal class CalcEngine
{
readonly XlsFile xls = new(1, FlexCel.Core.TExcelFileFormat.v2023, true);
readonly string ConfigFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "config.xlsx");
readonly string[] Predefined =
[
"5", "=A1 * 3 + 7", "=Sum(A1, A2) * 9", "=Sin(a1) + cos(a2)", "=Average(a1:a4)",
"", "", "", "", "", "", "", "", "", "", ""
];
public CalcEngine()
{
bool Restoring = false;
if (File.Exists(ConfigFile))
{
try
{
xls.Open(ConfigFile);
Restoring = true;
}
catch
{
//if the file is corrupt, we'll just ignore it.
//Restoring will be false, and we will create a new file.
}
}
if (!Restoring)
{
xls.NewFile(1, TExcelFileFormat.v2023);
for (int k = 0; k < Predefined.Length; k++)
{
xls.SetCellFromString(k + 1, 1, Predefined[k]); //Initialize the grid with something so users know what they have to do.
}
}
xls.Recalc();
}
public string GetRowValue(int row)
{
object cell = xls.GetCellValue(row, 1);
if (cell == null)
return "";
if (cell is TFormula fmla)
{
return Convert.ToString(fmla.Result) ?? "";
}
return Convert.ToString(cell) ?? "";
}
public string GetRowFormulaText(int row)
{
object cell = xls.GetCellValue(row, 1);
if (cell == null)
return "";
if (cell is TFormula fmla)
{
return fmla.Text ?? "";
}
return Convert.ToString(cell) ?? "";
}
public string SetRowFormula(int row, string value) {
xls.SetCellFromString(row, 1, value);
xls.Recalc();
Directory.CreateDirectory(Path.GetDirectoryName(ConfigFile)!);
xls.Save(ConfigFile);
return GetRowValue(row);
}
public int GetRowCount()
{
return Math.Min(xls.RowCount, 100); //ensure it isn't too big, we only want to display 100 rows.
}
}
}
MainActivity.cs
namespace FlexCalc
{
[Activity(Label = "@string/app_name")]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
var grid = FindViewById<ListView>(Resource.Id.grid)!;
var adapter = new CalcAdapter(this, App.CalcEngine);
grid.Adapter = adapter;
}
}
}
SplashActivity.cs
using Android.Content;
namespace FlexCalc
{
[Activity(Label = "@string/app_name", MainLauncher = true, Theme = "@style/SplashTheme", NoHistory = true)]
public class SplashActivity : Activity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_splash);
}
protected override void OnResume()
{
base.OnResume();
StartActivity(new Intent(this, typeof(MainActivity)));
}
}
}