watchOS - How to update a SwiftUI view from extension delegate - watchkit

I have an independent watch app which tracks users calories throughout the day.
So when I build and run the app for the first time through Xcode the onAppear(_:) method of the view is called and the calories are properly retrieved from the HealthKit repository.
Whenever I put my hand down and raise my wrist after 5 min, the applicationDidBecomeActive of the ExtensionDelegate method is called but the onAppear(_:) method of the SwiftUI view is not getting called(my HealthKit code to fetch calories is called in this function) and the screen show's the same number of calories the previous time the app ran through Xcode.
Is this expected behaviour? if yes then how can I update my SwiftUI view through the Extension Delegate?

I think the failure to invoke .onAppear on wrist-raise is a bug, and I filed feedback about it. In the meantime, I got this to work as a "wrist-raise" event.
// ExtensionDelegate.swift
extension Notification.Name {
static var applicationIsActive: Notification.Name {
Notification.Name("applicationIsActive")
}
}
class ExtensionDelegate: NSObject, WKExtensionDelegate {
func applicationDidBecomeActive() {
NotificationCenter.default.post(name: .applicationIsActive, object: nil)
}
}
// ContentView.swift
private var isActive = NotificationCenter.default.publisher(for: .applicationIsActive)
.onReceive(isActive) {_ in self.viewModel.refresh()}

Related

How can firebase phone auth be implemented in view model?

I'm trying to add the Firebase Phone Authentication code inside a View Model using Kotlin. The problem is that the PhoneAuthProvider requires an activity. Does anyone know how can this code be implemented inside a View Model without the need of an activity?
Thanks!
val mCallbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks ...
val options = PhoneAuthOptions.newBuilder(auth).apply {
setPhoneNumber(phoneNumber)
setTimeout(120L, TimeUnit.SECONDS)
setActivity(this) <-------------------------- // Activity (for callback binding)
setCallbacks(mCallbacks)
}.build()
PhoneAuthProvider.verifyPhoneNumber(options)
It turned out to be an intentional change in API 20 (check out this issue on Github), even though it violates the MVVM architecture. The reason an activity is needed is that the method falls back to reCAPTCHA. The right way to achieve it is "yet to be determined".
My approach is to include everything in the viewModel including the callbacks. I then call a function in the viewModel and pass in an activity parameter. see below:
fun verifyPhoneNumber(phoneNumber: String, activity: Activity) {
_validFullPhoneNumber.value = phoneNumber
val options = PhoneAuthOptions.newBuilder(mAuth)
.setPhoneNumber(phoneNumber) // Phone number to verify
.setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(activity)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
and in the UI controller, in my case a fragment I call it as:
viewModel.verifyPhoneNumber(validatedPhoneNumber, requireActivity())
same with resend button function.
viewModel:
fun resendVerificationCode(activity: Activity) {
val options =
PhoneAuthOptions.newBuilder(mAuth)
.setPhoneNumber(_validFullPhoneNumber.value!!) // Phone number to verify
.setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(activity)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
.setForceResendingToken(_resendToken) // ForceResendingToken from callbacks
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
_isVerificationCodeExpired.value = false
}
UI controller(fragment):
viewModel.resendVerificationCode(requireActivity())

SwiftUI firebase - change user's displayName - how to refresh view instantly

I've created a basic firebase login app using swiftui (using this tutorial: https://www.youtube.com/watch?v=FhLEwqyVSjE). Everything works as it should.
In my ContentView.swift I added a TextField for changing the user's display name. To do this, I added a changeDisplayName func in the SessionStore.swift with this logic:
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges { (error) in
// ...
}
The func is called by a button in my ContentView. Changing the displayName works aswell, but my ContentView still holds the old displayName until I restart the app, because only then the ContentView calls session.listen() again and gets the new displayName from the SessionStore.
To fix this, I call session.unbind() and then session.listen() in my ContentView whenever the changeDisplayName func in my SessionStore is called by my ContentView and doesn't return an error. This works and my ContentView is reloaded with the new displayName everytime it is changed.
Is this good practice or am I missing a cleaner solution to this problem?
Maybe try using Auth.auth().currentUser?.reload(). This may not work because I've had trouble in the past with isEmailVerified but I guess it's worth a try!

Firebase Realtime Database, ListAdapter, DiffUtil and ViewModel

I am trying to create a list view using Recycler View and display a list. Lets say what I am trying to display is like a typical chat screen - image, message, sender_name etc.
So all this data is stored in Firebase Realtime Database. I am also using ViewModel and would like to use DiffUtil for efficiency - since messages can be deleted, edited, starred, new ones added etc.. Due to DiffUtil, I am using a ListAdapter.
The issue I am facing is that the ListAdapter needs a List and I use a ChildEventListener.
How do I now observe changes from Firebase using LiveData and then update my list so that I can pass back into my DiffUtil? So if I add a new message, I'd like that to be added to my RecyclerView and I'd like to do that using DiffUtil since the list can also be updated.
What I found through research was that I might need to use Room for this purpose and observe the query for changes - so observe something like a getAllMessages() method which would return the complete list and then I can use that to pass into my DiffUtil. That sounds like an overkill to me - the implementation of Room.
Any pointers or suggestions on how I can achieve my need?
Here is the structure of my db:
if you dare to start programming an app, you won't be able to get along with Room soon. if you take this step, SQL knowledge is necessary as well...so don't be shy and just dare to do it
GGK
So I figured this out by doing the following in my ViewModel.
private val _message = MutableLiveData<List<TextListModel>>()
val message: LiveData<List<TextListModel>>
get() = _message
//...
private val eventListener =
object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
Timber.e("Error!! ${databaseError.toException()}")
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
val listOfMessages : MutableList<TextListModel> = mutableListOf()
for(msg in dataSnapshot.children) {
val item = msg.getValue(TextListModel::class.java)
listOfMessages.add(item!!)
}
_message.value = listOfMessages
}
}
In my fragment I observe message and perform adapter.submitList(newList) when there is an update.
This downloading of data using ValueEventListener is concerning though since it pulls the data from under the node. I am checking on how to use ChildEventListener for my purposes.
EDIT
Seems like Value events don't download the full tree over! Value events work with the data in the local state and only downloads the delta.
Some more information in this Youtube Video from 2016!

Prism navigation from within a Service only working when not navigating back to much

I have a Prism Xamarin.Forms App created with the Prism Template Pack (2.0.7).
All packages are up to date:
Xamarin.Forms v2.5.0.91635
Xamarin.Android.* v26.1.0.1
Prism.DryIoc.Forms v7.0.0.168-pre (Could not update to v7.0.0.269-pre due to a NullReferenceException in Android Renderer on startup, however my main application is using 269-pre without that problem)
I hosted the sample Application on https://github.com/dernippel/PrismNavApp
I have the following components:
Module "Attachments" (a Prism Module)
Module "Viewer" (a Prism Module)
Service "AttachmentService" registered with the Container with an Interface as Singleton
They should do this:
The AttachmentsPage (from the AttachmentsModule) lists some objects
Selecting one Attachment
AttachmentPageViewModel calls the "OpenAttachment"-Method of the Attachment Service
The Method determines the correct ViewerPage by type and uses the Prism-NavigationService to navigate directly to the Page (in the sample this is the "ImageViewerPage" of the ViewerModule)
This is only working when you do the following navigation:
MainPage -> AttachmentsPage -> ViewerPage -> (back Arrow) AttachmentsPage -> ViewerPage (and so on)
But if you navigate back to MainPage navigation to ViewerPage isn't working anymore:
MainPage -> AttachmentsPage -> ViewerPage -> (back Arrow) AttachmentsPage -> (back Arrow) MainPage -> AttachmentsPage -> (nothing happens anymore when tap on Button to navigate to ViewerPage)
The AttachmentsService gets the NavigationService via Constructor injection and navigates this way:
public AttachmentService(INavigationService navigationService)
{
this.navigationService = navigationService;
}
public async void OpenAttachmentWithViewer(object attachment)
{
// ToDo: handle parameter proberbly
var attachmentType = "image";
// select correct viewer
if (attachmentType == "image")
{
// navigate to image viewer
var navParams = new NavigationParameters();
navParams.Add("object",attachment);
var navTask = this.navigationService.NavigateAsync(
"ImageViewerPage",
navParams,
useModalNavigation: false);
await navTask;
var result = navTask.Status;
Debug.WriteLine($"Navigation State is {result}");
}
}
I tried to check the navigation Task result status, it is always "RanToCompletion".
Modifying the AttachmentsPageViewModel to navigate directly with the Prism NavigationService instead using the Service doesn't cause this behavior:
private void OnOpenAttachment()
{
// ToDo: get the selected attachment
object selectedAttachment = null;
// navigating inside a service -- not working when navigating back to MainPage
//this.attachmentService.OpenAttachmentWithViewer(selectedAttachment);
// navigation from view model -- working
var navParams = new NavigationParameters();
navParams.Add("object", selectedAttachment);
this.navigationService.NavigateAsync("ImageViewerPage", navParams, useModalNavigation: false);
}
Hint: I switched with my Main-Application from PCL-based to the new .NETStandard based solution and already had a similar functionality working successfully using Prism v6.3.0.1. This functionality is not even navigating once since the port.
Actually I don't know how to solve this.
Is it possible to have a look into the Prism NavigationService to determine why the navigation is not happen?
I didn't find any known bug in the Prism Repository yet.
You can't use the NavigationService in another service, especially if that service is a singleton. Navigation in Xamarin.Forms is specific to a Page, and only works in context of an associated page. Instead, your service should return a result and you should navigate from your VM based on that result. Don't try to navigate from within a service.

Event not working

I am new to Tridion Event System. I have written a small code.
[TcmExtension("MyEventHandlerExtension")]
public class EH : TcmExtension
{
public EH()
{
Subscribe();
}
public void Subscribe()
{
//EventSystem.Subscribe<Component, DeleteEventArgs>(HandlerForInitiated, EventPhases.Initiated);
EventSystem.Subscribe<Tridion.ContentManager.CommunicationManagement.Page, Tridion.ContentManager.Extensibility.Events.PublishOrUnPublishEventArgs>(HandlerForCommitted, EventPhases.All);
}
private void HandlerForCommitted(IdentifiableObject subject, PublishOrUnPublishEventArgs args, EventPhases phase)
{
TDSE obj = new TDSE();
Tridion.ContentManager.Interop.TDS.Publication pub = obj.GetPublication("tcm:0-150-1");
Tridion.ContentManager.Interop.TDS.Page pubPage = obj.GetPage("tcm:150-12374-64", pub);
pubPage.Publish("tcm:0-1-65538", false, true, false, default(DateTime), default(DateTime), default(DateTime));
}
}
using this code i wanted to publish a page everytime when a publish and unpublish event occur.
I build this code and register its path in tridion config file .
But its not working.Please Help
Ok, first of all remove all your TDSE code, you should use TOM.NET. You can get session as subject.Session
Then make sure you have registered this extension in Tridion.ContentManager.config and restarted your system
And finally - if something doesn't work, just add simple code that will create a file in your HandlerForCommitted whenever event occurs, this way you will be able to see if your extension get executed.
The 2011 Event System uses the TOM.NET API and not the TOM API. Please do not create new TDSE objects in the 2011 Event System. Even though you can reference the old Interop libraries, there is no reason to do so with 2011. Using the TOM.NET libraries you should see better performance and also the code is future-proof.
Mihai Cadariu has a nice example where he uses TOM.NET to Publish a page from a Tridion Template. Adjusting the code to check for previewmode or publish mode and setting your own user and priority (instead of reading it from the current transaction) should work well.
Below code from http://yatb.mitza.net/2012/05/publishing-from-template-code-using.html
public void Publish(Engine engine, String tcmUri, User user, PublishPriority priority)
{
Session session = new Session(user.Title);
PublishInstruction publishInstruction = new PublishInstruction(session);
RenderInstruction renderInstruction = new RenderInstruction(session);
renderInstruction.RenderMode = RenderMode.Publish; // work around. needs to be specified for binaries.
publishInstruction.RenderInstruction = renderInstruction;
List<IdentifiableObject> items = new List<IdentifiableObject>() { session.GetObject(tcmUri) };
List<PublicationTarget> targets = new List<PublicationTarget>() { engine.PublishingContext.PublicationTarget };
PublishEngine.Publish(items, publishInstruction, targets, priority);
session.Dispose();
}
// called with
PublishTransaction currentTransaction = TemplateUtils.GetPublishTransaction(engine);
TemplateUtils.Publish(engine, itemUri, currentTransaction.Creator, currentTransaction.Priority);
Your code seems to have the three things I "normally" forget:
the class is public
it extends TcmExtension
it has a TcmExtension attribute
If you've registered the class correctly in the configuration file, it should just be a matter of restarting the relevant module(s). In this case I'd expect those to be the Publisher and TcmServiceHost services.
After restarting those modules and triggering a publish action, you should see an event being logged (in the Windows event viewer) that your extension is being loaded.
If that even shows, it means your assembly is being loaded into the relevant Tridion process and the class is being recognized and instantiated.
If at this stage your handler doesn't fire you may have to consider listening to a different event. Whenever I want to interact with the publishing, I end up listening for the SaveEventArgs of a PublishTransaction, instead of the PublishOrUnPublishEventArgs on the Page.

Resources