Jetpack Compose Observe mutableStateOf in ViewModel - viewmodel

I have a need to update the user profile switch
ViewModel
class ProfileViewModel : BaseViewModel() {
var greet = mutableStateOf(user.pushSetting.greet)
var message = mutableStateOf(user.pushSetting.message)
var messageDetails = mutableStateOf(user.pushSetting.messageDetails)
var follow = mutableStateOf(user.pushSetting)
var like = mutableStateOf(user.pushSetting.like)
var comment = mutableStateOf(user.pushSetting.comment)
fun updateUser() {
println("--")
}
}
2.Composable
#Composable
fun SettingCard(viewModel: ProfileViewModel) {
Lists {
Section {
TextRow(text = "手机号码") { }
TextRow(text = "修改密码", line = false) { }
}
Section {
SwitchRow(text = "新好友通知", checkedState = viewModel.greet)
SwitchRow(text = "新消息通知", checkedState = viewModel.message)
SwitchRow(text = "消息显示详细", line = false, checkedState = viewModel.messageDetails)
}
}
}
3.SwitchRow
#Composable
fun SwitchRow(text: String, line: Boolean = true, checkedState: MutableState<Boolean>) {
ListItem(
text = { Text(text) },
trailing = {
Switch(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(checkedThumbColor = MaterialTheme.colors.primary)
)
}
)
}
How can I observe the change of the switch and call updateUser() in ViewModel
I know this is a way, but it is not ideal. The network update will be called every time it is initialized. Is there a better solution?
LaunchedEffect(viewModel.greet) {
viewModel.updateUser()
}

The best solution for this would be to have unidirectional flow with SwitchRow with a lambda as #Codecameo advised.
But if you want to observe a MutableState inside your Viewmodel you can use snapshotFlows as
var greet: MutableState<Boolean> = mutableStateOf(user.pushSetting.greet)
init {
snapshotFlow { greet.value }
.onEach {
updateUser()
}
.launchIn(viewModelScope)
//...
}
Create a Flow from observable Snapshot state. (e.g. state holders
returned by mutableStateOf.) snapshotFlow creates a Flow that runs
block when collected and emits the result, recording any snapshot
state that was accessed. While collection continues, if a new Snapshot
is applied that changes state accessed by block, the flow will run
block again, re-recording the snapshot state that was accessed. If the
result of block is not equal to the previous result, the flow will
emit that new result. (This behavior is similar to that of
Flow.distinctUntilChanged.) Collection will continue indefinitely
unless it is explicitly cancelled or limited by the use of other Flow
operators.

Add a callback lamba in SwitchRow and call it upon any state change
#Composable
fun SettingCard(viewModel: ProfileViewModel) {
Lists {
Section {
TextRow(text = "手机号码") { }
TextRow(text = "修改密码", line = false) { }
}
Section {
SwitchRow(text = "新好友通知", checkedState = viewModel.greet) {
viewModel.updateUser()
}
SwitchRow(text = "新消息通知", checkedState = viewModel.message) {
viewModel.updateUser()
}
SwitchRow(text = "消息显示详细", line = false, checkedState = viewModel.messageDetails) {
viewModel.updateUser()
}
}
}
}
#Composable
fun SwitchRow(
text: String,
line: Boolean = true,
checkedState: MutableState<Boolean>,
onChange: (Boolean) -> Unit
) {
ListItem(
text = { Text(text) },
trailing = {
Switch(
checked = checkedState.value,
onCheckedChange = {
onChange(it)
checkedState.value = it
},
colors = SwitchDefaults.colors(checkedThumbColor = MaterialTheme.colors.primary)
)
}
)
}
Another approach:
You can keep MutableStateFlow<T> in your viewmodel and start observing it in init method and send a value to it from SwitchRow, like viewModel.stateFlow.value = value.
Remember, MutableStateFlow will only trigger in the value changes. If you set same value twice it will discard second value and will execute for first one.
val stateFlow = MutableStateFlow<Boolean?>(null)
init {
stateFlow
.filterNotNull()
.onEach { updateUser() }
.launchIn(viewModelScope)
}
In switchRow
viewmodel.stateFlow.value = !(viewmodel.stateFlow.value?: false)
This could be one potential solution. You can implement it in your convenient way.

Related

Wren class does not implement a field

I'm trying to learn how to use Dome and Wren. To do this I've started working on a simple Flappy Bird clone.
The problem I'm having is that I get the error message
Bird does not implement bird_idle
It then points to line 63 in my main class which is shown below:
class Main {
resources=(value){ _resources = value }
bird=(value){ _bird = value }
construct new() {
_resources = Resources.new()
_bird = Bird.new(_resources.birdIdle, _resources.birdFlap, _resources.obstacleTile.width * 2, _resources.obstacleTile.height * 4, 10, 4)
}
init() {}
update() {}
draw(alpha) {
Canvas.cls()
Canvas.draw(_bird.bird_idle, _bird.center.x, _bird.center.y) <-- this is line 63
}
}
var Game = Main.new()
Since I'm new to Wren I don't quite understand what this means seeing as if you look at my bird class below, I should be implementing bird_idle. So what am I doing wrong?
class Bird {
bird_idle=(value){ _bird_idle = value } <-- Right?
bird_flap=(value){ _bird_flap = value }
center=(value){ _center = value }
gravity=(value){ _gravity = value }
force=(value){ _force = value }
velocity=(value){ _velocity = value }
velocityLimit=(value){ _velocityLimit = value }
isGameRunning=(value){ _isGameRunning = value }
construct new(idle, flap, horizontalPosition, verticalPosition, drag, jumpForce) {
_bird_idle = idle
_bird_flap = flap
_center = Vector.new(horizontalPosition + (idle.width * 0.5), verticalPosition + (idle.height*0.5))
_gravity = drag
_force = jumpForce
_velocityLimit = 1000
_isGameRunning = true
}
jump() {
_velocity = -_force
}
isTouchingCeiling() {
var birdTop = _center.y - (_bird_idle.height * 0.5)
if(birdTop < 0) {
return true
}
return false
}
isTouchingGround() {
var birdBottom = _center.y + (_bird_idle.height * 0.5)
if(birdBottom > height) {
return true
}
return false
}
}
You forgot to add the getter:
class Bird {
construct new(idle) {
_bird_idle = idle
}
bird_idle=(value){_bird_idle = value }
bird_idle{_bird_idle} // You need this if you want to access the field _bird_idle.
}
var my_bird = Bird.new("Idle")
my_bird.bird_idle = "Not Idle"
System.print(my_bird.bird_idle) // Output: Not Idle

Reduce script run time in Google Apps Script?

I created a script that tracks attendance for distance learning. After a while it times out so I think I am having issues with too many calls to the Google Classroom API, however I don't see a way that I can change it to take those calls out of a loop.
The script takes all the Google Classroom classes that my apps script account is a co-teacher on and using timed triggers creates a daily attendance assignment with one question that says 'here'. Students are then supposed to answer the question and then another trigger at night runs the function to 'grade' each assignment and populate my spreadsheet so school secretaries can view it in the morning and record the previous days attendance.
The part that seems to have the bottleneck is my getStudentResponses() function. I tried to reduce time by filtering out students that didn't submit the assignment, but it wasn't enough. Does anyone see any way that I can make this faster? I was reading up on using the Cache Service, but I couldn't figure out how to get that to work. Any help would be appreciated.
var ss = SpreadsheetApp.getActive();
var date = new Date();
/*
creates a button to programmatically create all necessary timed triggers for easy deployment
*/
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Attendance')
.addItem('Create Triggers', 'createTriggers')
.addToUi();
}
/*
auto accepts any co-teacher invites
*/
function acceptInvite() {
try{
var optionalArgs = {
userId: "me"
};
var invites = Classroom.Invitations.list(optionalArgs);
for(var i = 0; i < invites.invitations.length; i++) {
Classroom.Invitations.accept(invites.invitations[i].id);
}
}
catch(e){}
}
/*
populates a spreadsheet with all the classes that the script Google account is a co-teacher of
the sheet has two columns one with the course name and two with the course id
*/
function listCourses() {
var optionalArgs = {courseStates: "ACTIVE"};
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
var classSheet;
try{
classSheet = ss.insertSheet("Classes", 0);
ss.insertSheet("Assignments", 1);
}
catch(e) {
classSheet = ss.getSheetByName("Classes");
}
classSheet.clear();
if (courses && courses.length > 0) {
for (i = 0; i < courses.length; i++) {
var course = courses[i];
classSheet.appendRow([course.name, course.id]);
}
}
}
/*
reads the sheet to get all the classes and creates a new array with all the class IDs
*/
function getCourses() {
var classSheet = ss.getSheetByName("Classes");
var classList = new Array();
var range = classSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[1]+"";
classList.push(Classroom.Courses.get(courseId));
}
createTopics(classList);
}
/*
called immediatly after getCourses, creates topics in each class that will contain the daily attendance assignment
*/
function createTopics(classList) {
for(i = 0; i < classList.length; i++) {
var topic;
var resource = {name: "Daily Online Attendance"};
try {
topic = Classroom.Courses.Topics.create(resource, classList[i].id);
createAssignment(topic,classList[i]);
}
catch(e) {
if(e == "GoogleJsonResponseException: API call to classroom.courses.topics.create failed with error: Requested entity already exists") {
var topics = Classroom.Courses.Topics.list(classList[i].id);
for(j = 0; j < topics.topic.length; j++) {
if(topics.topic[j].name == "Daily Online Attendance") {
createAssignment(topics.topic[j], classList[i]);
}
}
}
}
}
}
/*
creates an assignment in each class, under each topic
each assignment only has one choice that says "here" and is going to be 'graded' each night to track attendance
*/
function createAssignment(topic,course) {
var resource = {
title: "Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020",
description: "Please fill this assignment out each day for attendance",
topicId: topic.topicId,
state: "PUBLISHED",
workType: "MULTIPLE_CHOICE_QUESTION",
multipleChoiceQuestion: {
"choices": [
"Here"
]
}
};
try {
var assignment = Classroom.Courses.CourseWork.create(resource, course.id);
var sheet = ss.getSheetByName("Assignments");
sheet.appendRow([course.id,assignment.id]);
}
catch(e){}
}
/*
creates a new sheet for each day and logs each assignement
*/
function getStudentResponses() {
var assignmentSheet = ss.getSheetByName("Assignments");
var sheet2;
var response;
assignmentSheet.sort(1, true);
try{
sheet2 = ss.insertSheet("Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020",(ss.getSheets().length-(ss.getSheets().length-2)));
sheet2.appendRow(["Student Last Name","Student First Name","Grade","Class Name","Assignment Answer"]);
}
catch(e) {
sheet2 = ss.getSheetByName("Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020");
}
sheet2.setFrozenRows(1);
var range = assignmentSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[0]+"";
var courseWorkId = row[1]+"";
try {
response = Classroom.Courses.CourseWork.StudentSubmissions.list(courseId, courseWorkId);
for(var j in response.studentSubmissions) {
if(response.studentSubmissions[j].state == "TURNED_IN") {
try {
var grade;
var email = Classroom.UserProfiles.get(response.studentSubmissions[j].userId).emailAddress;
sheet2.appendRow([Classroom.UserProfiles.get(response.studentSubmissions[j].userId).name.familyName,Classroom.UserProfiles.get(response.studentSubmissions[j].userId).name.givenName,grade,Classroom.Courses.get(courseId).name,response.studentSubmissions[j].multipleChoiceSubmission.answer]);
}
catch (e) {}
}
}
}
catch(e) {}
}
}
/*
deletes all assignemnts that were created
*/
function deleteAssignments() {
var assignmentSheet = ss.getSheetByName("Assignments");
assignmentSheet.sort(1, true);
var range = assignmentSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[0]+"";
var courseWorkId = row[1]+"";
try {
Classroom.Courses.CourseWork.remove(courseId, courseWorkId);
}
catch(e) {}
assignmentSheet.clear();
}
}
function createTriggers() {
ScriptApp.newTrigger('getCourses')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
ScriptApp.newTrigger('getStudentResponses')
.timeBased()
.everyDays(1)
.atHour(22)
.create();
ScriptApp.newTrigger('deleteAssignments')
.timeBased()
.everyDays(1)
.atHour(23)
.create();
ScriptApp.newTrigger('listCourses')
.timeBased()
.everyDays(1)
.atHour(21)
.create();
ScriptApp.newTrigger('acceptInvite')
.timeBased()
.everyDays(1)
.atHour(20)
.create();
}
appendRow is slow, you should avoid to used it inside a for loop. Instead build an array, then pass the values using a single setValues call.
Resources
Best Practices | Apps Script
Related
Google Script Performance Slow Down
Increase my script performance Google Sheets Script
Very slow execution of for...in loop

XUnit Test for ViewComponent returns null result?

I am trying to test my ViewComponent with XUnit.
When I debug through the component and set a break point right before it returns the Component View, the model is set.
Here is the simple model I am returning.
public class IntDashMakeRecAssgnmntsPoRespMngrVM
{
public IEnumerable<Audit> Audits { get; set; }
}
And I am trying to assert the Audits.Count() is greater than 0.
Here is my View Component:
public class IntDashMakeRecAssgnmntsPoRespMngrViewComponent : ViewComponent
{
private IAuditRepository _auditRepo;
private IExternalRepository _externalRepo;
public IntDashMakeRecAssgnmntsPoRespMngrViewComponent(IAuditRepository auditRepo,
IExternalRepository externalRepo)
{
_auditRepo = auditRepo;
_externalRepo = externalRepo;
}
public IViewComponentResult Invoke()
{
ClaimsPrincipal user = HttpContext.Request.HttpContext.User;
short staffId = short.Parse(user.Claims.Single(c => c.Type == "StaffId").Value);
// Get all Internal Audits that are not closed and not completed
var audits = _auditRepo.Audits
.Include(a => a.Findings).ThenInclude(f => f.Recommendations).ThenInclude(r => r.Assignments)
.Where(a => a.StatusID != 3 && a.StatusID != 11);
var external = _externalRepo.ExternalRecords;
audits = audits.Where(a => !external.Any(e => e.AuditID == a.AuditID));
if (User.IsInRole("PAG_SPEC") && !User.IsInRole("PAG_ADMIN_INT"))
{
audits = audits.Where(a =>
a.Assignments.Any(assn => assn.AssignmentAuditId == a.AuditID
&& assn.AssignmentRoleId == 2 && assn.AssignmentStaffId == staffId));
}
// Where audit has a recommendation without an assigned PO Authorizer
// OR without an assigned Responsible Manager (Rec Level).
List<Audit> auditsToAssign = new List<Audit>();
foreach (Audit audit in audits)
{
foreach (Finding finding in audit.Findings)
{
foreach (Recommendation rec in finding.Recommendations)
{
if (!rec.Assignments.Any(asgn => asgn.AssignmentRoleId == 15)
|| !rec.Assignments.Any(asgn => asgn.AssignmentRoleId == 26)
)
{
auditsToAssign.Add(rec.Finding.Audit);
break;
}
}
}
}
IntDashMakeRecAssgnmntsPoRespMngrVM intDashMakeRecAssgnmntsPoRespMngrVM =
new IntDashMakeRecAssgnmntsPoRespMngrVM
{
Audits = auditsToAssign
};
return View("/Views/InternalAudit/Components/Dashboard/IntDashMakeRecAssgnmntsPoRespMngr/Default.cshtml", intDashMakeRecAssgnmntsPoRespMngrVM);
}
}
When I get to this line in debugging and break to inspect, I have 1 Audit which I want:
return View("/Views/InternalAudit/Components/Dashboard/IntDashMakeRecAssgnmntsPoRespMngr/Default.cshtml", intDashMakeRecAssgnmntsPoRespMngrVM);
Now here is my Unit Test:
[Fact]
public void ReturnsAudit_1Finding_1Rec_1Asgn_PONeeded_RespMnrAssigned()
{
// Arrange
var audits = new Audit[]
{
new Audit { AuditID = 1 }
};
var findings = new Finding[]
{
new Finding{ Audit = audits[0], FindingId = 1 } // 1 Finding
};
var recommendations = new List<Recommendation>()
{
new Recommendation // 1 Rec
{
Finding = findings[0],
Assignments = new List<Assignment>()
{
// PO Authorizor
new Assignment { AssignmentRoleId = 15 }
// No Responsible Manager
}
}
};
audits[0].Findings = findings;
findings[0].Recommendations = recommendations;
Mock<IAuditRepository> mockAuditRepo = new Mock<IAuditRepository>();
mockAuditRepo.Setup(m => m.Audits).Returns(audits.AsQueryable());
Mock<IExternalRepository> mockExternalRepo = new Mock<IExternalRepository>();
mockExternalRepo.Setup(m => m.ExternalRecords).Returns(
new External[0].AsQueryable()
);
// Act
var component = new IntDashMakeRecAssgnmntsPoRespMngrViewComponent(
mockAuditRepo.Object, mockExternalRepo.Object);
component.ViewComponentContext = new ViewComponentContext();
component.ViewComponentContext.ViewContext.HttpContext = TestContext;
var result =
component.Invoke() as IntDashMakeRecAssgnmntsPoRespMngrVM;
int auditCount = (result).Audits.Count();
// Assert
Assert.Equal(1, auditCount);
}
Why is result null on this line?
var result =
component.Invoke() as IntDashMakeRecAssgnmntsPoRespMngrVM;
I also tried this first and it is still null:
ViewComponentResult result =
component.Invoke() as ViewComponentResult;
int auditCount =
((IntDashMakeRecAssgnmntsPoRespMngrVM)result.Model).Audits.Count();
I figured it out.
I wasn't casting the result to the right type.
I had this:
ViewComponentResult result =
component.Invoke() as ViewComponentResult;
int auditCount =
((IntDashMakeRecAssgnmntsPoRespMngrVM)result.Model).Audits.Count();
It should be this:
var result =
component.Invoke() as ViewViewComponentResult;
int auditCount =
((IntDashMakeRecAssgnmntsPoRespMngrVM)result.ViewData.Model).Audits.Count();
ViewViewComponentResult instead of ViewComponentResult.

Can't advertise on bluetooth

I want to create a Gatt Server in my Xamarin.Forms app so that other devices can scan for it via bluetooth. I am using this plugin:
https://github.com/aritchie/bluetoothle
This is my code to create a Gatt Server and advertise data:
server = CrossBleAdapter.Current.CreateGattServer();
var service = server.AddService(serviceGuid, true);
var characteristic = service.AddCharacteristic(
characteristicGuid,
CharacteristicProperties.Read |
CharacteristicProperties.Write | CharacteristicProperties.WriteNoResponse,
GattPermissions.Read | GattPermissions.Write
);
var notifyCharacteristic = service.AddCharacteristic
(
notifyCharacteristicGuid,
CharacteristicProperties.Indicate | CharacteristicProperties.Notify,
GattPermissions.Read | GattPermissions.Write
);
IDisposable notifyBroadcast = null;
notifyCharacteristic.WhenDeviceSubscriptionChanged().Subscribe(e =>
{
var #event = e.IsSubscribed ? "Subscribed" : "Unsubcribed";
if (notifyBroadcast == null)
{
notifyBroadcast = Observable
.Interval(TimeSpan.FromSeconds(1))
.Where(x => notifyCharacteristic.SubscribedDevices.Count > 0)
.Subscribe(_ =>
{
Debug.WriteLine("Sending Broadcast");
var dt = DateTime.Now.ToString("g");
var bytes = Encoding.UTF8.GetBytes("SendingBroadcast");
notifyCharacteristic.Broadcast(bytes);
});
}
});
characteristic.WhenReadReceived().Subscribe(x =>
{
var write = "HELLO";
// you must set a reply value
x.Value = Encoding.UTF8.GetBytes(write);
x.Status = GattStatus.Success; // you can optionally set a status, but it defaults to Success
});
characteristic.WhenWriteReceived().Subscribe(x =>
{
var write = Encoding.UTF8.GetString(x.Value, 0, x.Value.Length);
Debug.WriteLine("in WhenWriteReceived() value: " + write);
// do something value
});
await server.Start(new AdvertisementData
{
LocalName = "DariusServer",
ServiceUuids = new List<Guid>() { serverServiceGuid }
});
I am using this app to scan for my advertisement data:
https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp
I can't discover my app with it. I don't know what I'm doing wrong? I am testing with a real device, SM-T350 tablet
I spent countless hours to get this plugin to work with no luck. But this native code works for anyone else who has the same problem:
private async Task AndroidBluetooth()
{
try
{
await Task.Delay(5000); // just to make sure bluetooth is ready to go, this probably isn't needed, but good for peace of mind during testing
BluetoothLeAdvertiser advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser;
var advertiseBuilder = new AdvertiseSettings.Builder();
var parameters = advertiseBuilder.SetConnectable(true)
.SetAdvertiseMode(AdvertiseMode.Balanced)
//.SetTimeout(10000)
.SetTxPowerLevel(AdvertiseTx.PowerHigh)
.Build();
AdvertiseData data = (new AdvertiseData.Builder()).AddServiceUuid(new ParcelUuid(Java.Util.UUID.FromString("your UUID here"))).Build();
MyAdvertiseCallback callback = new MyAdvertiseCallback();
advertiser.StartAdvertising(parameters, data, callback);
}
catch(Exception e)
{
}
}
public class MyAdvertiseCallback : AdvertiseCallback
{
public override void OnStartFailure([GeneratedEnum] AdvertiseFailure errorCode)
{
// put a break point here, in case something goes wrong, you can see why
base.OnStartFailure(errorCode);
}
public override void OnStartSuccess(AdvertiseSettings settingsInEffect)
{
base.OnStartSuccess(settingsInEffect);
}
}
}
Just to note, it wouldn't work if if I included the device name, because the bluetooth transmission would be too large in that case with a service UUID (max 31 bytes I believe).

LinearSeqOptimized#find Reference Copy

Looking at the Scala 2.10.0's implementation of LinearSeqOptimized#find in LinearSeqOptimized.scala, why is it necessary to call var these = this?
Why couldn't this simply be used?
override /*IterableLike*/
def find(p: A => Boolean): Option[A] = {
var these = this
while (!these.isEmpty) {
if (p(these.head)) return Some(these.head)
these = these.tail
}
None
}
Because you would have to have the same condition and operation out of the loop for this and then start using these.
It's much simpler to just put everyone in the same basket and do it all in the loop itself. Example:
def find(p: A => Boolean): Option[A] = {
if (!this.isEmpty && p(this.head)) {
return Some(this.head)
}
var these = this.tail
while (!these.isEmpty) {
if (p(these.head)) return Some(these.head)
these = these.tail
}
None
}
Not very smart, as you can see.
You could also easily implement this as a #tailrec operation:
#tailrec final def find[A](p : A => Boolean) : Option[A] = {
if ( this.isEmpty ) {
None
} else {
if ( p(this.head) ) {
Some(this.head)
} else {
this.tail.find(p)
}
}
}
And it isn't done like this in Scala because tailrec calls have to be final or private.

Resources