How model a updatable view for a dynamic form builder and listen to their changes - forms

I'm building a server-driven UI (aka: a form builder) that get the form definition dynamically in JSON, then renders that form.
I need to update the values and send back the results. Some fields also need to trigger validations, so I need to listen to them.
Doing this with JetPack is confusing (to me) because it is not clear how I architect the form builder.
The data for the form builder is:
#Serializable
enum class Action {
Create,
Update,
}
#Serializable
data class ErrorMsg(
val message: String,
val kind: ErrorKind
)
#Serializable
data class Form(
var main_action: Action?,
var title: String?,
var errors: ArrayList<ErrorMsg>
var sections: ArrayList<FormSection>,
)
// A form section is a group of fields
#Serializable
data class FormSection(
var id: UInt,
var fields: ArrayList<FormField>,
var title: String?
)
#Serializable
data class FormField(
val field: String,
val obligatory: Boolean,
val error: ErrorMsg?,
var value: String, //<-- This is what needs to trigger changes!
)
So, if I try to pass the form to a view:
#Composable
fun ComposablePreview() {
val f1 = UIData.FormField.txt("1", "Code", "001")
val f2 = UIData.FormField.pwd("2", "Pwd", "123")
val fields = arrayListOf(f1, f2)
val sections =
UIData.FormSection(
id = 0u, fields = fields, "Sample")
val form_base = UIData.Form(
main_action = null, sections= arrayListOf(sections))
val form by remember { mutableStateOf(form_base) }
FormView(form = form)
}
How get the changes propagated?
I build each form control alike
#Composable
fun TextCtrlView(
field: UIData.FormField,
) {
var value by remember { mutableStateOf(field.value) }
TextField(
label = { Text(field.label) },
value = value,
isError = field.error != null,
onValueChange = { value = it },
)
}

Related

LIveData is observed more than twice everytime from acivity and emitting previous data

I follow MVVM Login API, with Retrofit ,My problem is livedata is observed more than twice and always emitting previous response when observed from activity, But inside Repository its giving correct response
I tried a lot of solutions from stackoverflow and other websites but still no luck, Tried removing observers also but still getting previous data ,so plz suggest a working solution, I will post my code below,
LoginActivity.kt
private lateinit var loginViewModel: LoginViewModel
loginViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(
LoginViewModel::class.java
)
loginViewModel.login(userEmail, pwd)
loginViewModel.getLoginRepository().observe(this, Observer {
val loginResult = it ?: return#Observer
val accessToken = loginResult.user?.jwtToken.toString()
})
val statusMsgObserver = Observer<String> { statusMsg ->
showToast(statusMsg)
})
val errorMsgObserver = Observer<String> { errorMsg ->
// Update the UI
showToast(errorMsg)
})
loginViewModel.getStatusMessage()?.observe(this, statusMsgObserver)
loginViewModel.getErrorStatusMessage()?.observe(this, errorMsgObserver)
LoginViewModel.kt:
class LoginViewModel: ViewModel() {
private var loginRepository: LoginRepository? = null
private var _mutableLiveData = MutableLiveData<LoginAPIResponse?>()
val liveData: LiveData<LoginAPIResponse?> get() = _mutableLiveData
private var responseMsgLiveData:MutableLiveData<String>?= null
private var errorResponseMsgLiveData:MutableLiveData<String>?= null
fun login(username: String, password: String) {
loginRepository = LoginRepository.getInstance()!!
/* Query data from Repository */
//val _mutableLiveData: MutableLiveData<Response<LoginAPIResponse?>?>? = loginRepository?.doLogin(username, password)
_mutableLiveData = loginRepository?.doLogin(username, password)!!
responseMsgLiveData = loginRepository?.respMessage!!
errorResponseMsgLiveData = loginRepository?.loginResponseErrorData!!
}
fun getLoginRepository(): LiveData<LoginAPIResponse?> {
return liveData
}
fun getStatusMessage(): LiveData<String>? {
return responseMsgLiveData
}
fun getErrorStatusMessage(): LiveData<String>? {
return errorResponseMsgLiveData
}
}
LoginRepository.kt:
class LoginRepository {
private val loginApi: ApiEndpoints = RetrofitService.createService(ApiEndpoints::class.java)
val responseData = MutableLiveData<LoginAPIResponse?>()
var respMessage = MutableLiveData<String>()
var loginResponseErrorData = MutableLiveData<String>()
fun doLogin(username: String, password: String)
: MutableLiveData<LoginAPIResponse?> {
respMessage.value = null
loginResponseErrorData.value = null
val params = JsonObject()
params.addProperty("email", username)
params.addProperty("password",password)
val jsonParams = JsonObject()
jsonParams.add("user",params)
loginApi.loginToServer(jsonParams).enqueue(object : Callback<LoginAPIResponse?> {
override fun onResponse( call: Call<LoginAPIResponse?>, response: Response<LoginAPIResponse?> ) {
responseData.value = response.body()
respMessage.value = RetrofitService.handleError(response.code())
val error = response.errorBody()
if (!response.isSuccessful) {
val errorMsg = error?.charStream()?.readText()
println("Error Message: $errorMsg")
loginResponseErrorData.value = errorMsg
} else {
println("API Success -> Login, $username, ${response.body()?.user?.email.toString()}")
}
}
override fun onFailure(call: Call<LoginAPIResponse?>, t: Throwable) {
println("onFailure:(message) "+t.message)
loginResponseErrorData.value = t.message
responseData.value = null
}
})
return responseData
}
companion object {
private var loginRepository: LoginRepository? = null
internal fun getInstance(): LoginRepository? {
if (loginRepository == null) {
loginRepository = LoginRepository()
}
return loginRepository
}
}
}
In onDestroy(),I have removed the observers,
override fun onDestroy() {
super.onDestroy()
loginViewModel.getLoginRepository()?.removeObservers(this)
this.viewModelStore.clear()
}
In LoginActivity, when I observe loginResult it gives previous emitted accessToken first and then again called and giving current accessToken, Similarly observer is called more than twice everytime.
But inside repository,its giving recent data, plz check my code and suggest where I have to correct to get correct recent livedata
Finally i found the solution, In LoginRepository, I declared responseData outside doLogin(), It should be declared inside doLogin()
Since it was outside the method, it always gave previous data first and then current data,
Once I declare inside method problem was solved and now it is working Perfect!!!

Interested Challenge: Is it possible to make a class according to a given prototype?

Say: there is already a given schema definition object:
const schema = { prop1: { type: String, maxLength: 8 }, prop2... };
Is it possible that: without declare the interface for each schema object, make a respective class which can produce documents with prop1:string, prop2... extracted from the schema.
I expect something like this in my app:
// schema definition:
const PersonSchema = { name: { type: String, maxLength: 8 } };
// class factory
const PersonClass = SchemaClassFactory(PersonSchema);
// instance with props defined in schema.
let person1 = new PersonClass();
person1.name = 'Jack';
let person2 = new PersonClass();
person2.name = 3; // should be wrong hinted by tslint.
How can I achieve that?
You can create a class for the schema object using a mapped type and conditional types to extract the shape of the object from the schema.
A possible solution is below, I am not sure I covered all the ways you can defined the schema in mongoose, but this should get you stared:
const PersonSchema = {
name: { type: String, maxLength: 8 },
age: { type: Number },
title: String,
id: ObjectID
};
type PrimitiveConstructor<T> = {
new (...a: any[]): any;
(...a: any[]): T
}
type Constructor<T> = {
new (...a: any[]): T;
}
type ExtractType<T> = {
[P in keyof T] :
T[P] extends PrimitiveConstructor<infer U>? U :
T[P] extends { type: PrimitiveConstructor<infer U> } ? U:
T[P] extends Constructor<infer U> ? U :
T[P] extends { type: Constructor<infer U> } ? U:
never
}
function createClass<T>(schema: T): new (data?: Partial<ExtractType<T>>) => ExtractType<T> {
// The class will not have the fields explicitly defined since we don't know them but that is fine
return new class {
// Optional constructor for assigning data to the fields, you can remove this if not needed
constructor(data?: any){
if(data) {
Object.assign(this, data);
}
}
} as any;
}
var PersonClass = createClass(PersonSchema);
type PersonClass = InstanceType<typeof PersonClass>
let p = new PersonClass();
p.name ="";
p.name = 2; // error
p.id = new ObjectID(10);
let p2 = new PersonClass({
name: "",
});

how to create an updateable tableview cell in Scala

I have created a tableview with their components inside it, assigned cellValueFactory and have set the properties editable to true. Somewhere in my code, I have the following :
...
tableID.selectionModel().selectedItem.onChange(
(_, _, newValue) => col_uname.setCellFactory(TextFieldTableCell.forTableColumn());
...
With it, I managed to create to convert it to textfield and are allowed to type in it. However,after finishing typing, the text reversed back to previous text before the edit. What type/piece of code should I include make sure that the text is updated properly?
I've tried searching on google, but there's no explanation for it so far.
You should be able to edit table by, as you mentioned, editable = true and adding cell factory with a text field, for instance:
new TableColumn[Person, String] {
text = "First Name"
cellValueFactory = {_.value.firstName}
cellFactory = TextFieldTableCell.forTableColumn()
prefWidth = 180
}
The JavaFX Table View Tutorial also suggests using OnEditCommit. Not sure if that is really necessary. Here is a complete example that works without using OnEditCommit:
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.StringProperty
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.TableColumn._
import scalafx.scene.control.cell.TextFieldTableCell
import scalafx.scene.control.{Button, TableColumn, TableView}
import scalafx.scene.layout.VBox
object EditableTableView extends JFXApp {
class Person(firstName_ : String, lastName_ : String) {
val firstName = new StringProperty(this, "firstName", firstName_)
val lastName = new StringProperty(this, "lastName", lastName_)
firstName.onChange { (_, oldValue, newValue) => println(s"Value changed from `$oldValue` to `$newValue`") }
lastName.onChange { (_, oldValue, newValue) => println(s"Value changed from `$oldValue` to `$newValue`") }
override def toString = firstName() + " " + lastName()
}
val characters = ObservableBuffer[Person](
new Person("Peggy", "Sue"),
new Person("Rocky", "Raccoon")
)
stage = new PrimaryStage {
title = "Editable Table View"
scene = new Scene {
root = new VBox {
children = Seq(
new TableView[Person](characters) {
editable = true
columns ++= List(
new TableColumn[Person, String] {
text = "First Name"
cellValueFactory = {_.value.firstName}
cellFactory = TextFieldTableCell.forTableColumn()
prefWidth = 180
},
new TableColumn[Person, String]() {
text = "Last Name"
cellValueFactory = {_.value.lastName}
cellFactory = TextFieldTableCell.forTableColumn()
prefWidth = 180
}
)
},
new Button {
text = "Print content"
onAction = (ae: ActionEvent) => {
println("Characters:")
characters.foreach(println)
}
}
)
}
}
}
}

Unable to cast object of type Entity to Type ActivityParty

Im working with a custom plugin for CRM online 2015 and every time I try to access the activityparty from the field "Email.To" I get
"base {System.SystemException} = {"Unable to cast object of type 'Microsoft.Xrm.Sdk.Entity' to type ...ActivityParty'."}"
Here is how my code looks like:
public class PreCreate : Plugin
{
public PreCreate()
: base(typeof(PreCreate))
{
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Create", "email", new Action<LocalPluginContext>(ExecutePreEntityCreate)));
}
public void ExecutePreEntityCreate(LocalPluginContext localContext)
{
var target = (Entity)localContext.PluginExecutionContext.InputParameters["Target"];
using (var context = new XrmServiceContext(localContext.OrganizationService))
{
var email = target.ToEntity<Email>(); //The entity has the right values
var activityPartyList=email.To // here I see the exception
//If I use the following code:
var activityParty = email.GetAttributeValue<EntityCollection>("to");
//I get an empty ActivityParty(empty Id)
}
}
}
Do I have to do some initialization for activityparty types?
There is no issue with the code, the field Email.To will return a EntityCollection and to obtain that you need to use:
var entityCollection = email.GetAttributeValue<EntityCollection>("to");
This will give you a collection of entities that need to be converted to ActivityParty(entityCollection.Entities).
To convert the Entities you need to:
foreach (var entityItem in entityCollection.Entities)
{
var ap = entityItem.ToEntity<ActivityParty>();
//Here you will get the LogicalName in this case Lead
// the Id and the name
var leadId = ap.PartyId.Id;
//To get the Lead
var lead=context.LeadSet.FirstOrDefault(l => l.Id == leadId);
}

UnitTesting EF6 with OfType<T>

I am trying to follow the guidelines provided http://msdn.microsoft.com/en-us/library/dn314429.aspx by Microsoft for Unittesting DbSets. All was going well - as they documented. Until I got to some code which works with a inheritance table. Since OfType() is an extension method, I cannot figure out how to create a Mock which will work to keep my code testable.
To clarify: I am trying to Test My Service Layer, which take a DBContext which is Injected, and which exposes several DbSets. In particular, I have an abstract History class, which has concrete derived types of StaffHistory, ContactHistory, etc. As a result, I only have 1 DbSet on my Dbcontext, which is of type History. I then use the Extension method OfType to set the discriminator and query the particular type.
When I create a Mock DbSet all usually works fine, except the OfType extension method fails, reporting NullReference Exception.
Any ideas or tips?
Service Layer:
public IEnumerable<ContactHistory> GetContactHistory(int ContactId, int AgeInDays)
{
var age = DateTimeOffset.Now.AddDays(-Math.Abs(AgeInDays));
return context.History.OfType<ContactHistory>()
.Where(h => h.ContactId == ContactId && h.CreatedAt >= age)
.AsEnumerable();
}
Unit Test Code:
[TestMethod]
public void History_Returns_Limited_Results()
{
var testData = new List<ContactHistory> {
new ContactHistory {
ContactId = 1,
CreatedAt = DateTimeOffset.Now,
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = DateTimeOffset.Now.AddDays(-61),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = DateTimeOffset.Now.AddDays(-60),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = DateTimeOffset.Now,
UserName = "UserA",
Action = "Action",
}
}.AsQueryable();
// Setup
var mockContext = new Mock<IPEContext>();
var mockSet = new Mock<IDbSet<History>>();
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.Provider).Returns(testData.Provider);
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.Expression).Returns(testData.Expression);
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.GetEnumerator()).Returns(testData.GetEnumerator());
mockContext.Setup(c => c.History).Returns(mockSet.Object);
// Test
var service = new HistoryService(mockContext.Object);
var historyFound = service.GetContactHistory(4, 60);
// Verify
Assert.IsNotNull(historyFound);
Assert.AreEqual(2, historyFound.Count());
}
Is there something flawed in my approach? Is there something flawed in how I have setup my mock? This was following the Microsoft Article I mentioned above so that I could test service logic acting on a DbSet. The only flaw seems to be the Extension Method - not sure how I should work around that.
OK - I have figured this out. Of course there was a simple answer, but one which eluded me, because I had already mapped the Linq Provider and all in as the Type IQueryable. If you are using the .OfType() method, your mock must return on the Untyped Queryable method.
Here is the test code to allow the Method to work properly:
[TestMethod]
public void History_Returns_Limited_Results()
{
var today = new DateTimeOffset(DateTime.Today, DateTimeOffset.Now.Offset);
var testData = new List<ContactHistory> {
new ContactHistory {
ContactId = 1,
CreatedAt = today,
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = today.AddDays(-61),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = today.AddDays(-60),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = today,
UserName = "UserA",
Action = "Action",
}
}.AsQueryable();
// Setup
var mockContext = new Mock<IPEContext>();
var mockSet = new Mock<IDbSet<History>>();
mockSet.As<IQueryable>().Setup(m => m.Provider).Returns(testData.Provider);
mockSet.As<IQueryable>().Setup(m => m.Expression).Returns(testData.Expression);
mockSet.As<IQueryable>().Setup(m => m.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable>().Setup(m => m.GetEnumerator()).Returns(testData.GetEnumerator());
mockContext.Setup(c => c.History).Returns(mockSet.Object);
// Test
var service = new HistoryService(mockContext.Object);
var contact = new Person { ContactId = 4 };
var historyFound = service.GetContactHistory(contact, 60);
// Verify
Assert.IsNotNull(historyFound);
Assert.AreEqual(2, historyFound.Count());
}