Where to put and how to read from manually created DB file using Xamarin forms? - sql

I created questions.db file using DB Browser.
Here it is: My lovely .db file
I want to add it to Xamarin.Forms solution and read data from it. (This is going to be quiz app)
That sounds pretty simple, right, but I'm stuck googling for hours. Most answers just link to this https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
Which explains nothing about the manually created database file, where to put it etc.
EDIT: this is what I'm trying to achieve: https://arteksoftware.com/deploying-a-database-file-with-a-xamarin-forms-app/
Sadly, the guide is outdated and many things of it doesn't work.
So where do I put my .db file? In MyApp.Android Assets and Resources for MyApp.iOS? If so, how do I get DBpath string then for new connection?
_database = new SQLiteAsyncConnection(dbPath);

If what you want to do is:
Add a pre existing database to your project and load that database to be used by your App then keep on reading.
Short Answer
You need to add your database file to a specific folder in the paltform project (Assets in Android, in iOS you can simply create a folder and put the file there) and use DependencyService to access it. Copy the database file to somewhere in your device (App data folder or InternalStorage or watever is allowed on each platform) and use that final path in the SQLite constructor. From now on, you can simply use that SQLite connection as any other to perform CRUD operation on your Database.
Long Answer
I will now describe how to achieve this step by step from creating a new Xamarin.Forms project to reading data from the preloaded database.
Note: the follwoing solution will only cover the Android part of the CrossPlatform solution. Extending this to iOS should be no problem.
Disclaimer: The code presented next should just be taken as a guideline and by no means I suggest that is proved or production like.
0. Getting project ready:
Create a new Xamarin.Forms project (on my case i use Visual Studio v. 16.3.8 and the new project has Xamarin.Forms 4.2 installed)
Then, first of all, install sqlite nuget package in ALL your projects:
1. Prepare your Database
In App.xaml.cs file
// Create db static property to perform the
// Database calls from around the project
public static Database db { get; set; }
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
Create a new class file and name it Database.cs. There you open the database connection.
public class Database
{
// Create a SQLiteAsyncConnection property to be accessed
// publicly thru your App.
public SQLiteAsyncConnection DBInstance { get; set; }
public Database(String databasePath)
{
DBInstance = new SQLiteAsyncConnection(databasePath);
}
private async Task<string> GetDatabaseFilePath()
{
return await DependencyService.Get<IPathFinder>().GetDBPath();
}
}
So far so good, but... What should actually be the path of your preloaded database?
Well, that is the next part.
2. Load pre-existent database to your project and get a path to it (Android)
So the big question is where to store your pre-existent database in the project.
One option is to add it as an Asset. To implement this approach, do the following:
2.1 Add database file to Assets folder in Android project
Now we want to access that database. To do so, follow the next steps:
2.2 Create an Interface IPathFinder in your App project
And there define a single member GetDBPath()
public interface IPathFinder
{
Task<String> GetDBPath();
}
Now, in your Android project create a class file PathFinder.cs to implement this interface
and implement the method (Note the Dependency Attribute above the namespace!)
using System;
using SysIO = System.IO;
using System.Threading.Tasks;
using Java.IO;
using Xamarin.Forms;
using TestDB.Droid;
[assembly: Dependency(typeof(PathFinder))]
namespace TestDB.Droid
{
public class PathFinder : IPathFinder
{
public async Task<string> GetDBPath()
{
String dbPath = String.Empty;
if (await PermissonManager.GetPermission(PermissonManager.PermissionsIdentifier.Storage))
{
String systemPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.RootDirectory.Path).Path;
String tempFolderPath = SysIO::Path.Combine(systemPath, "MytestDBFolder");
if (!SysIO::File.Exists(tempFolderPath))
{
new File(tempFolderPath).Mkdirs();
}
dbPath = SysIO::Path.Combine(tempFolderPath, "test.db");
if (!SysIO::File.Exists(dbPath))
{
Byte[] dbArray;
using (var memoryStream = new SysIO::MemoryStream())
{
var dbAsset = MainActivity.assets.Open("test.db");
dbAsset.CopyTo(memoryStream);
dbArray = memoryStream.ToArray();
}
SysIO.File.WriteAllBytes(dbPath, dbArray);
}
}
return dbPath;
}
}
}
In the GetDBPath() method, the first to note is the GetPermission method. This is needed since Android API 23 in order to manage the App permissions.
Create a file called PermissonManager in your Android project
And add the code below
using System;
using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
namespace TestDB.Droid
{
public class PermissonManager
{
public enum PermissionsIdentifier
{
Storage // Here you can add more identifiers.
}
private static String[] GetPermissionsRequired(PermissionsIdentifier identifier)
{
String[] permissions = null;
if (identifier == PermissionsIdentifier.Storage)
permissions = PermissionExternalStorage;
return permissions;
}
private static Int32 GetRequestId(PermissionsIdentifier identifier)
{
Int32 requestId = -1;
if (identifier == PermissionsIdentifier.Storage)
requestId = ExternalStorageRequestId;
return requestId;
}
public static TaskCompletionSource<Boolean> PermissionTCS;
public static readonly String[] PermissionExternalStorage = new String[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
public const Int32 ExternalStorageRequestId = 2;
public static async Task<Boolean> GetPermission(PermissionsIdentifier identifier)
{
Boolean isPermitted = false;
if ((Int32)Build.VERSION.SdkInt < 23)
isPermitted = true;
else
isPermitted = await GetPermissionOnSdk23OrAbove(GetPermissionsRequired(identifier), GetRequestId(identifier));
return isPermitted;
}
private static Task<Boolean> GetPermissionOnSdk23OrAbove(String[] permissions, Int32 requestId)
{
PermissionTCS = new TaskCompletionSource<Boolean>();
if (MainApplication.CurrentContext.CheckSelfPermission(permissions[0]) == (Int32)Permission.Granted)
PermissionTCS.SetResult(true);
else
ActivityCompat.RequestPermissions((Activity)MainApplication.CurrentContext, permissions, requestId);
return PermissionTCS.Task;
}
public static void OnRequestPermissionsResult(Permission[] grantResults)
{
PermissionTCS.SetResult(grantResults[0] == Permission.Granted);
}
}
}
In that class you note the presence of MainApplication, which provides the CurrentContext. You will also have to add that class file
and there add the following code
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
namespace DemoDB.Droid
{
[Application]
public partial class MainApplication : Application, Application.IActivityLifecycleCallbacks
{
private static Context _currentContext = Application.Context;
internal static Context CurrentContext
{
get => _currentContext;
private set
{
_currentContext = value;
}
}
internal static String FileProviderAuthority
{
get => MainApplication.CurrentContext.ApplicationContext.PackageName + ".fileprovider";
}
public MainApplication(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}
public override void OnCreate()
{
base.OnCreate();
RegisterActivityLifecycleCallbacks(this);
}
public override void OnTerminate()
{
base.OnTerminate();
UnregisterActivityLifecycleCallbacks(this);
}
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
CurrentContext = activity;
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
CurrentContext = activity;
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
CurrentContext = activity;
}
public void OnActivityStopped(Activity activity)
{
}
}
}
Then your PermissonManager is almost ready. Now you just have to override OnRequestPermissionsResult in the MainActivity file
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
PermissonManager.OnRequestPermissionsResult(grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
Getting back to the GetPath() method, you see a mysterious MainActivity.assets property call. This has to be created in the MainActivity as follows
public static AssetManager assets;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
assets = this.Assets;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
Almost ready! Now you just call your database.
3. Use your database!
From the OnAppearing of the Main page, make a simple call to the database, to create it and access it.
protected override async void OnAppearing()
{
String databasePath = await Database.GetDatabaseFilePath();
App.db = new Database(databasePath);
var table = await App.db.DBInstance.CreateTableAsync<Category>();
// here Category is a class that models the objects
// present in my pre-existent database
List<Category> categories = new List<Category>();
categories = await App.db.DBInstance.Table<Category>().ToListAsync();
base.OnAppearing();
}
And that is it.
I hope this is helpful :P

Related

Why does 'InputField' not contain a definition for 'text'?

I'm currently working on a Unity project for a college assignment, and I'm currently trying to connect a login/registration through PlayFab into a teammate's main menu for the game.
I've connected the PlayFabManager.cs script to the Input Fields for the email and password in the Unity editor, and something about my InputFields.cs file is preventing me from making any more progress.
I had to change the passwordInput and emailInput variables to TMP_InputField variables to achieve this, but now I am getting a compilation error in my project that says the following:
Assets\Scripts\InputField.cs(13,24): error CS1061: 'InputField' does not contain a definition for 'text' and no accessible extension method 'text' accepting a first argument of type 'InputField' could be found (are you missing a using directive or an assembly reference?)
Most places I look have people not including the "using UnityEngine.UI;" header at the top of the file, but that's included in my InputField.cs file.
Here's the code for my InputField.cs file:
using UnityEngine;
using System.Collections;
using UnityEngine.UI; // Required when Using UI elements.
public class InputField : MonoBehaviour
{
public InputField mainInputField;
public void Start()
{
mainInputField.text = "Enter text here...";
}
}
Here's the code for my PlayFabManager.cs file:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;
using Newtonsoft.Json;
using UnityEngine.UI;
using TMPro; // Needed for login input fields
public class PlayFabManager : MonoBehaviour
{
[Header("UI)")]
public Text messageText;
public TMP_InputField emailInput;
public TMP_InputField passwordInput;
// Register/Login/ResetPassword
public void RegisterButton() {
var request = new RegisterPlayFabUserRequest {
Email = emailInput.text,
Password = passwordInput.text,
RequireBothUsernameAndEmail = false
};
PlayFabClientAPI.RegisterPlayFabUser(request, OnRegisterSuccess, OnError);
}
void OnRegisterSuccess(RegisterPlayFabUserResult result) {
messageText.text = "Registered and Logged in";
}
public void LoginButton() {
}
// Start is called before the first frame update
void Start() {
Login();
}
// Update is called once per frame
void Login() {
var request = new LoginWithCustomIDRequest {
CustomId = SystemInfo.deviceUniqueIdentifier,
CreateAccount = true
};
PlayFabClientAPI.LoginWithCustomID(request, OnSuccess, OnError);
}
void OnSuccess(LoginResult result) {
Debug.Log("Successful login/account create.");
}
void OnError(PlayFabError error) {
Debug.Log("Error while loggin in/creating account.");
Debug.Log(error.GenerateErrorReport());
}
}
I would just remove the InputField.cs class as it fixes my errors, but it changes the functionality of the following code that my teammate has contributed:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class MenuControl : MonoBehaviour
{
public string newGameLevel;
public void NewUser() {
SceneManager.LoadScene(newGameLevel);
}
public void ExitButton() {
Application.Quit();
}
}
Any help would be much appreciated!
Wanted to provide the solution in case this happens to anyone in the future:
I solved the problem by changing the
public InputField mainInputField;
into an input variable that could receive the TMP_Imput like so: public TMPro.TMP_InputField mainInputField;

Best way to insert high trafic into CosmosDb with Entity Framework Core

we have ASP.NET Core 6.0 website and want to write logging data into CosmosdDB.
Therefor i have a static CosmosDB Manager, which has the CosmosDBContext as static property, and the add method uses this context to AddLog() and SaveContext().
I had to put the Context in a lock, as multiple calls to the AddLog() method caused an error.
Is this the right way to have (concurrent) Writes to the CosmosDB?
I had previously separate Context for each request, but this opened to many threads...
Any thoughts on this Implementation?
public static class LogManager {
private static CosmosDBContext Context { get; set; }
public static void AddLog(string data) {
var item = new LogItem {
Data = data
};
if (Context == null) Context = new();
lock (Context) {
Context.Add(item);
Context.SaveChanges();
}
}
}
public class LogItem {
public string Data { get; set; }
}

How to write Xunit test case of factory design pattern code block which is tightly coupled?

I would like to write xunit test case of below method. Could you please suggest alternate design so i can write xunit test case with minimum change in my current project.
public ActionResult Index(int id = 0, AssetFilterType filter = AssetFilterType.All)
{
using (var tracer = new Tracer("AssetController", "Index"))
{
RemoveReturnUrl();
ViewBag.JobId = id;
var response = ContextFactory.Current.GetDomain<EmployeeDomain>().GetEmployeeFilterAsync(id,
CurrentUser.CompanyId, filter); // Not able write unit test case , please suggest alternate design.
return View("View", response);
}
}
current design is as follow
public interface IDomain
{
}
public interface IContext
{
D GetDomain<D>() where D : IDomain;
string ConnectionString { get; }
}
public class ApplicationContext : IContext
{
public D GetDomain<D>() where D : IDomain
{
return (D)Activator.CreateInstance(typeof(D));
}
public string ConnectionString
{
get
{
return "DatabaseConnection";
}
}
}
public class ContextFactory
{
private static IContext _context;
public static IContext Current
{
get
{
return _context;
}
}
public static void Register(IContext context)
{
_context = context;
}
}
//var response = ContextFactory.Current.GetDomain**< EmployeeDomain>**().GetEmployeeFilterAsync(id,
CompanyId, filter);
This line serve purpose to call specific class method i.e GetEmployeeFilterAsync from EmployeeDomain. Although it is very handy and widely used in our application but due to design issue i am not able to write unit
test case.
Could you please suggest design so with the minimum change we can write unit test case.
Don't use the Service Locator anti-pattern, use Constructor Injection instead. I can't tell what AssetDomain is from the OP, but it seems as though it's the dependency that matters. Inject it into the class:
public class ProbablySomeController
{
public ProbablySomeController(AssetDomain assetDomain)
{
AssetDomain = assetDomain;
}
public AssetDomain AssetDomain { get; }
public ActionResult Index(int id = 0, AssetFilterType filter = AssetFilterType.All)
{
using (var tracer = new Tracer("AssetController", "Index"))
{
RemoveReturnUrl();
ViewBag.JobId = id;
var response = AssetDomain.GetAssetFilterAsync(id, CurrentUser.CompanyId, filter);
return View("View", response);
}
}
}
Assuming that AssetDomain is a polymorphic type, you can now write a test and inject a Test Double:
[Fact]
public void MyTest()
{
var testDouble = new AssetDomainTestDouble();
var sut = new ProbablySomeController(testDouble);
var actual = sut.Index(42, AssetFilterType.All);
// Put assertions here
}
step1 : Required library
step 2 : When the application starts , register required domain like
protected void Application_Start()
UnityConfig.RegisterComponents();
Step 3: create one static class and register all your domain
example
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
Initialize domain which will injected in controller
container.RegisterType<IPricingDomain, PricingDomain>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
step 4 :
so you can inject respective interface in constructor
in controller file.
goal : get rid of below any pattern in your project.
and start writing unit test cases.

Pass data from android service to ContentPage in Xamarin Form based application

I am having one Application based on XamarinForms.
One background service I have created in Android project and that service would like to send data to ContentPage(which is in PCL) which is displayed to user.
How could I pass data to ContentPage(From xx.Droid project to PCL)?
One solution is:
To Create class in PCL with static variable(e.g. var TEMP_VAR), which will be accessed from xxx.Droid project.
Update value of that static variable(TEMP_VAR) from the service class from the xxx.Droid project.
Need to create Notifier on that static variable(TEMP_VAR)
Update the content page using MessageCenter Mechanism if require.
If there is any better solution, could you please provide me?
This can be achieved using the concept of C#
Dependency service
Event
Need to have 4 classes for such an implementation:
Interface in PCL(e.g. CurrentLocationService.cs) with event handlers defined in it.
namespace NAMESPACE
{
public interface CurrentLocationService
{
void start();
event EventHandler<PositionEventArgs> positionChanged;
}
}
Implementation of interface of PCL in xxx.Droid project (e.g. CurrentLocationService_Android.cs) using Dependency service
class CurrentLocationService_Android : CurrentLocationService
{
public static CurrentLocationService_Android mySelf;
public event EventHandler<PositionEventArgs> positionChanged;
public void start()
{
mySelf = this;
Forms.Context.StartService(new Intent(Forms.Context, typeof(MyService)));
}
public void receivedNewPosition(CustomPosition pos)
{
positionChanged(this, new PositionEventArgs(pos));
}
}
ContentPage in PCL - which will have object of implementation of interface.
Object can be obtained by
public CurrentLocationService LocationService
{
get
{
if(currentLocationService == null)
{
currentLocationService = DependencyService.Get<CurrentLocationService>();
currentLocationService.positionChanged += OnPositionChange;
}
return currentLocationService;
}
}
private void OnPositionChange(object sender, PositionEventArgs e)
{
Debug.WriteLine("Got the update in ContentPage from service ");
}
Background service in xxx.Droid project. This service will have reference of implementation of dependency service CurrentLocationService.cs
[Service]
public class MyService : Service
{
public string TAG = "MyService";
public override IBinder OnBind(Intent intent)
{
throw new NotImplementedException();
}
public override StartCommandResult OnStartCommand(Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
Log.Debug(TAG, TAG + " started");
doWork();
return StartCommandResult.Sticky;
}
public void doWork()
{
var t = new Thread(
() =>
{
Log.Debug(TAG, "Doing work");
Thread.Sleep(10000);
Log.Debug(TAG, "Work completed");
if(CurrentLocationService_Android.mySelf != null)
{
CustomPosition pos = new CustomPosition();
pos.update = "Finally value is updated";
CurrentLocationService_Android.mySelf.receivedNewPosition(pos);
}
StopSelf();
});
t.Start();
}
}
Note : PositionEventArgs class need to be created as per usage to pass on data between service and ContentPage.
This works for me like charm.
Hope so this would be helpful to you.

Setting internal properties in composite WF4 Activities at design time

I want to create a composite Windows Workflow Activity (under .NET 4) that contains a predefined ReceiveAndSendReply Activity. Some of the properties are predefined, but others (particularly ServiceContractName) need to be set in the designer.
I could implement this as an Activity Template (the same way ReceiveAndSendReply is implemented), but would rather not. If I later change the template, I'd have to update all previously created workflows manually. A template would also permit other developers to change properties that should be fixed.
Is there a way to do this from a Xaml Activity? I have not found a way to assign an Argument value to a property of an embedded Activity. If not, what technique would you suggest?
I haven't done this using a composite XAML activity and am getting some errors when I try but doing so through a NativeActivity is no problem. See the example code below.
public class MyReceiveAndSendReply : NativeActivity
{
private Receive _receive;
private SendReply _sendReply;
public string ServiceContractName { get; set; }
public string OperationName { get; set; }
protected override bool CanInduceIdle
{
get { return true; }
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
_receive = _receive ?? new Receive();
_sendReply = _sendReply ?? new SendReply();
_receive.CanCreateInstance = true;
metadata.AddImplementationChild(_receive);
metadata.AddImplementationChild(_sendReply);
_receive.ServiceContractName = ServiceContractName;
_receive.OperationName = OperationName;
var args = new ReceiveParametersContent();
args.Parameters["firstName"] = new OutArgument<string>();
_receive.Content = args;
_sendReply.Request = _receive;
var results = new SendParametersContent();
results.Parameters["greeting"] = new InArgument<string>("Hello there");
_sendReply.Content = results;
base.CacheMetadata(metadata);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(_receive, ReceiveCompleted);
}
private void ReceiveCompleted(NativeActivityContext context, ActivityInstance completedInstance)
{
context.ScheduleActivity(_sendReply);
}
}