Is there a way to start an activity email with Live Data with ViewModel? - email

I understand that you cannot pass the activity nor the fragment to the view model as parameter and in order to start events or update the UI by click events it is preferable to use LiveData. However, I don't know how to start an email activity with LiveData. As far as I'm concerned you cannot start an activity on the class view model. This is the code that I have. (The lines in comments are just an examples, I know they won't work for this).
MainActivity.kt
val obvserver = Observer<String> {studentEmail.setOnClickListener{ intent = Intent(Intent.ACTION_SEND)
intent.data = Uri.parse("mailto:")
intent.type = "message/rfc822"
intent.putExtra(Intent.EXTRA_EMAIL, selectedStudent.email)
startActivity(Intent.createChooser(intent, "Send Email"))}}
studentEmail.setOnClickListener {
//viewModel.
}
ViewModel.kt
val studentEmail : MutableLiveData<String> by lazy {
MutableLiveData<String>()}

Related

how to sync firebase and local data model in flutter

Pretty positive I'm just totally overthinking this or approaching it from an illogical angle.
I'm separating my logic from my ui where button presses call a method located in the userModel which has a change notifier (getting passed into MyApp with a provider). I'm trying to implement firebase but have never called firebase directly from the ui (always just had the requests in the ui code, never used a model).
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
//TODO: firebase has been installed now I need to figure out how to implement it
// TODO: add firebase read and write
class UserModel with ChangeNotifier {
//index = session number
List session = [];
//create an instance of firebase (this might need to go higher in the tree)
var db = FirebaseFirestore.instance;
//TODO:
//convert incoming firebase to JSON
//convert outgoing json to firebase
//track session
// takes in current session data and adds the new chunk
// already recorded (new item in list but time, breaks etc. are adding from the last one)
// IF ADDING TO EXISTING, ALL PARAMETRS MUST BE SET
addSessionChunk(
{required String intention,
int workTime = 0,
String progress = "null",
int breakTime = 0}) {
session.add({
"intention": intention,
"workTime": workTime,
"progress": progress,
"breakTime": breakTime,
});
//firebase update?
}
//TODO: when returning to a previous intention, add to the numbers that were
//TODO: currently only works for 1 call per chunk (no going back to the same intention)
//get previous data from this intention for returning to a task (do
//these update functions updadate the LAST CHUNK in the session
updateChunkWorkTime({required int workTime}) {
//this later)
session.last["workTime"] = workTime;
}
//takes in inputed progress and updates the latest chunk with it
updateChunkProgress({required String progress}) {
session.last["progress"] = progress;
}
//takes inputed breaktime and updates the lastest chunk with it
updateChunkBreakTime({required int breakTime}) {
session.last["breakTime"] = breakTime;
}
//returns tuple of the total time spent working and breaking in the current session
calculateSessionTimeTotal() {
int totalWorkTime = 0;
int totalBreakTime = 0;
for (var chunk in session) {
totalWorkTime += chunk["workTime"] as int;
totalBreakTime += chunk["breakTime"] as int;
}
return Tuple2(totalWorkTime, totalBreakTime);
}
//firebase functions
pushDataUp() {
db.collection("sessions").doc().set({
"currentSession": session,
"total": calculateSessionTimeTotal().toString()
});
}
pullDataDown() {}
}
You can see at the bottom there I started to try and come up with a way to sync the local data state with firebase but am confused. Seems weird for the user to send their data up to firebase then back down into the model which is already holding that data?
Whats the best approach to local model and cloud database interaction? Any guidance in the right direction is greatly appreciated.
What seems weird to you, is actually a quite well defined patterns known as command query responsibility segregation, and is the basic pattern behind most modern UI frameworks. By separating the command (the writing of the data here) from the query (the reading of the data here) each remains simpler, and the total app becomes much easier to reason about.
With Firestore in Flutter, this usually translates into:
The user takes some action.
Your code writes to the database.
Your onSnapshot listener gets triggered with the updated data.
Your code updates the data model/state with the new data.
Which then renders the updated UI.
All of this happens pretty instantly, as Firebase actually handles it locally before even sending the data to the server and handles any exception that may occur during the synchronization with the server.

Why 'link' variable gets changed to null even after i assign it a value

private fun shareOperation(file: File) {
val uri = Uri.fromFile(file)
val storage = FirebaseStorage.getInstance()
val pdfRef = storage.reference.child("pdf/${uri.lastPathSegment}")
pdfRef.putFile(uri).addOnFailureListener { e ->
Log.e(TAG, "Couldn't share " + e.message)
}.addOnCompleteListener{
it.addOnCompleteListener {
pdfRef.downloadUrl.addOnSuccessListener { e ->
run {
link = e.toString()
Log.i(TAG,link!!) // Here i get the link to file in firebase storage
}
}
}
}
// Here link gets null
}
i was expecting somehow i can get the link to the file and can use it for sharing intent
You are performing an asynchronous call to upload the file, that is correct since any UI blocking action must be performed in background. The variable link will be null until the run code is executed in the background thread.
You need to code inside the run block whatever you want to happen when the link is available.
BTW looks weird what you are doing with the nested addOnCompleteListener, there should be an easier way to code that. You should probably spend time learning how to code with listeners and background threads.

Creating a user-controllable RxSwift Observer

I'm trying to implement user-driven refreshing in my Rx based networking code, and my current design is as follows:
Create a sink that has Void values passed into it every time the user initiates a refresh action
flatMap the latest .Next event on that sink's Observable into a new network call
Transform the network response into a new view model and pass that back into the view controller
The part I'm getting hung up on is how to create a sink for those events to go down. My current code is as follows:
func contactListModel() -> Observable<ContactListViewModel<Contact>> {
// Create a sink for refresh events
var refreshSink: AnyObserver<Void> = AnyObserver { event in }
let refreshObservable = Observable<Void>.create { observer in
refreshSink = observer
return NopDisposable.instance
}
// Define action handlers
let searchClosure = { (query: String?) in
self.contactsSearchTerm.value = query
}
let refreshClosure = refreshSink.onNext
// TODO: [RP] Make contact list view controller handle a nil view model to remove the need for this code
let initialViewModel = ContactListViewModel<Contact>(contacts: [], searchClosure: searchClosure, refreshClosure: refreshClosure)
// Perform an initial refresh
defer {
refreshSink.onNext()
}
// Set up subscription to push a new view model each refresh
return refreshObservable
.flatMapLatest {
return self.networking.request(.ListContacts)
}
.mapToObject(ListContactsResponse)
.map { response in
return ContactListViewModel(contacts: response.contacts, searchClosure: searchClosure, refreshClosure: refreshClosure)
}
.startWith(initialViewModel)
}
Now it's obvious why my code to create an event sink doesn't work here. The block being passed into refreshObservable's create method is only called once the observer is subscribed to, so the refreshSink won't be reassigned until then. Furthermore, if this observable is subscribed to more than once, the refreshSink variable will be reassigned.
So my question is this: how do I create an Observable that I can manually push events down? Or alternatively, is there a better design I could be using here?
I know ReactiveCocoa has the pipe static method on Signal that will do something like what I'm looking for, but I've found no equivalent in the Rx API.

RXJS : Idiomatic way to create an observable stream from a paged interface

I have paged interface. Given a starting point a request will produce a list of results and a continuation indicator.
I've created an observable that is built by constructing and flat mapping an observable that reads the page. The result of this observable contains both the data for the page and a value to continue with. I pluck the data and flat map it to the subscriber. Producing a stream of values.
To handle the paging I've created a subject for the next page values. It's seeded with an initial value then each time I receive a response with a valid next page I push to the pages subject and trigger another read until such time as there is no more to read.
Is there a more idiomatic way of doing this?
function records(start = 'LATEST', limit = 1000) {
let pages = new rx.Subject();
this.connect(start)
.subscribe(page => pages.onNext(page));
let records = pages
.flatMap(page => {
return this.read(page, limit)
.doOnNext(result => {
let next = result.next;
if (next === undefined) {
pages.onCompleted();
} else {
pages.onNext(next);
}
});
})
.pluck('data')
.flatMap(data => data);
return records;
}
That's a reasonable way to do it. It has a couple of potential flaws in it (that may or may not impact you depending upon your use case):
You provide no way to observe any errors that occur in this.connect(start)
Your observable is effectively hot. If the caller does not immediately subscribe to the observable (perhaps they store it and subscribe later), then they'll miss the completion of this.connect(start) and the observable will appear to never produce anything.
You provide no way to unsubscribe from the initial connect call if the caller changes its mind and unsubscribes early. Not a real big deal, but usually when one constructs an observable, one should try to chain the disposables together so it call cleans up properly if the caller unsubscribes.
Here's a modified version:
It passes errors from this.connect to the observer.
It uses Observable.create to create a cold observable that only starts is business when the caller actually subscribes so there is no chance of missing the initial page value and stalling the stream.
It combines the this.connect subscription disposable with the overall subscription disposable
Code:
function records(start = 'LATEST', limit = 1000) {
return Rx.Observable.create(observer => {
let pages = new Rx.Subject();
let connectSub = new Rx.SingleAssignmentDisposable();
let resultsSub = new Rx.SingleAssignmentDisposable();
let sub = new Rx.CompositeDisposable(connectSub, resultsSub);
// Make sure we subscribe to pages before we issue this.connect()
// just in case this.connect() finishes synchronously (possible if it caches values or something?)
let results = pages
.flatMap(page => this.read(page, limit))
.doOnNext(r => this.next !== undefined ? pages.onNext(this.next) : pages.onCompleted())
.flatMap(r => r.data);
resultsSub.setDisposable(results.subscribe(observer));
// now query the first page
connectSub.setDisposable(this.connect(start)
.subscribe(p => pages.onNext(p), e => observer.onError(e)));
return sub;
});
}
Note: I've not used the ES6 syntax before, so hopefully I didn't mess anything up here.

Hide debug messages from FeedbackMessageFilter in wicket 1.5 during production?

In developing some of my Web Applications I use debug("stuff here"); to help me figure out whats going on. I'd rather not have to go in and find all of my debug(*); lines and remove them. Is there a way I can specify at the application level not to display debug messages in a FeedBackPanel?
You could add an ErrorLevelFeedbackMessageFilter to your FeedbackPanels (there is a constructor accepting one). If you create the filter based on your deployment mode, this should be what you need.
In Detail:
Change your new FeedbackPanel("id") to new FeedbackPanel("id", Application.getFeedbackMessageFilter()) and implement this method as
public IFeedbackMessageFilter getFeedbackMessageFilter() {
IFeedbackMessageFilter filter = null;
if (RuntimeConfigurationType.DEVELOPMENT.equals(getConfigurationType())) {
filter = new ErrorLevelFeedbackMessageFilter(FeedbackMessage.DEBUG);
} else {
filter = new ErrorLevelFeedbackMessageFilter(FeedbackMessage.ERROR);
}
return filter;
}