I am trying to connect two services using RabbitMQ queue. First services pushes value to queue, second retrieves it and process. All is fine, but when second service tries to process job, it throws exception. Queue item stays in JobAttempt queue without any information, and consumer service retries to process job, but throws same exception every time.
Exception
fail: MassTransit.ReceiveTransport[0]
S-FAULT rabbitmq://localhost/JobAttempt f0cb0000-1616-902e-edb0-08d97cd26cf9 MassTransit.Contracts.JobService.JobStatusCheckRequested
fail: MassTransit.ReceiveTransport[0]
T-FAULT rabbitmq://localhost/JobAttempt f0cb0000-1616-902e-8129-08d97cca994f
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at MassTransit.Saga.InMemoryRepository.InMemorySagaRepositoryContext`2.Delete(SagaConsumeContext`1 context)
at MassTransit.Saga.MissingSagaPipe`2.Send(SagaConsumeContext`2 context)
at MassTransit.Saga.MissingSagaPipe`2.Send(SagaConsumeContext`2 context)
at MassTransit.Saga.SendSagaPipe`2.Send(SagaRepositoryContext`2 context)
at MassTransit.Saga.InMemoryRepository.InMemorySagaRepositoryContextFactory`1.Send[T](ConsumeContext`1 context, IPipe`1 next)
at MassTransit.Saga.Pipeline.Filters.CorrelatedSagaFilter`2.GreenPipes.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next)
at MassTransit.Saga.Pipeline.Filters.CorrelatedSagaFilter`2.GreenPipes.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next)
at MassTransit.Pipeline.Filters.InMemoryOutboxFilter`2.Send(TContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.InMemoryOutboxFilter`2.Send(TContext context, IPipe`1 next)
at GreenPipes.Filters.RetryFilter`1.GreenPipes.IFilter<TContext>.Send(TContext context, IPipe`1 next)
at GreenPipes.Partitioning.Partition.Send[T](T context, IPipe`1 next)
at GreenPipes.Filters.TeeFilter`1.<>c__DisplayClass5_0.<<Send>g__SendAsync|1>d.MoveNext()
--- End of stack trace from previous location ---
at GreenPipes.Filters.OutputPipeFilter`2.SendToOutput(IPipe`1 next, TOutput pipeContext)
at GreenPipes.Filters.OutputPipeFilter`2.SendToOutput(IPipe`1 next, TOutput pipeContext)
at GreenPipes.Filters.DynamicFilter`1.<>c__DisplayClass10_0.<<Send>g__SendAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
at MassTransit.Pipeline.Filters.DeserializeFilter.Send(ReceiveContext context, IPipe`1 next)
at GreenPipes.Filters.RescueFilter`2.GreenPipes.IFilter<TContext>.Send(TContext context, IPipe`1 next)
at GreenPipes.Filters.RescueFilter`2.GreenPipes.IFilter<TContext>.Send(TContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.DeadLetterFilter.GreenPipes.IFilter<MassTransit.ReceiveContext>.Send(ReceiveContext context, IPipe`1 next)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.RabbitMqTransport.Pipeline.RabbitMqBasicConsumer.<>c__DisplayClass24_0.<<HandleBasicDeliver>b__0>d.MoveNext()
Producer startup:
services.AddMassTransit(x =>
{
x.SetKebabCaseEndpointNameFormatter();
x.UsingRabbitMq((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService();
Consumer startup:
services.AddMassTransit(x =>
{
x.AddDelayedMessageScheduler();
x.AddConsumer<LoanRequestJobConsumer>(cfg =>
{
cfg.Options<JobOptions<LoanRequestBroker>>(options =>
{
options.SetJobTimeout(TimeSpan.FromMinutes(5));
options.SetConcurrentJobLimit(10);
});
});
x.SetKebabCaseEndpointNameFormatter();
x.UsingRabbitMq((context, cfg) =>
{
cfg.UseDelayedMessageScheduler();
cfg.ServiceInstance(instance =>
{
instance.ConfigureJobServiceEndpoints(js =>
{
js.SagaPartitionCount = 1;
js.FinalizeCompleted = true;
});
cfg.ReceiveEndpoint("loan-request-processing", e =>
{
e.ConfigureConsumer<LoanRequestJobConsumer>(context);
});
instance.ConfigureEndpoints(context);
});
});
});
services.AddMassTransitHostedService();
Job consumer
public class LoanRequestJobConsumer : IJobConsumer<LoanRequestBroker>
{
private readonly ILogger<LoanRequestJobConsumer> _logger;
private readonly ILoanProcessingService _processingService;
public LoanRequestJobConsumer(
ILogger<LoanRequestJobConsumer> logger,
ILoanProcessingService processingService)
{
_logger = logger;
_processingService = processingService;
}
public async Task Run(JobContext<LoanRequestBroker> context)
{
_logger.LogInformation($"{nameof(LoanRequestJobConsumer)}: start processing loan request id = {context.Job.Id}");
var processingInfo = new LoanProcessingInfo
{
Status = TaskStatus.InProgress,
LoanRequest = context.Job.Adapt<LoanRequest>()
};
processingInfo = await _processingService.SaveProcessingInfoAsync(processingInfo);
processingInfo = await _processingService.ProcessAsync(processingInfo);
processingInfo = await _processingService.SaveProcessingInfoAsync(processingInfo);
_logger.LogInformation($"{nameof(LoanRequestJobConsumer)}: end processing loan request id = {context.Job.Id}" +
$"\nResult: {JsonConvert.SerializeObject(processingInfo)}");
}
}
How I push item to the queue
var endpoint = await _sendEndpointProvider.GetSendEndpoint(_brokerEndpoints.LoanProcessingQueue);
await endpoint.Send(loanRequest.Adapt<LoanRequestBroker>());
If I had to guess, without any other error log details, I would think that the delayed exchange plug-in is not installed/enabled on RabbitMQ.
Related
For some reason, my Firebase Realtime Database adds another layer when encoding my data. I am new to using Firebase services, so maybe I entered an incorrect link or smh. -N-1sGl-7VrhyIG7PdDa should not appear. I have a slight idea of why it's happening, but I don't know how to access that last part. Thanks in advance!
Future<void> AddUserGoals(
String userId, String kcal, String p, String c, String f, BuildContext context) async {
final url = Uri.parse(
'https://recipier-e1139-default-rtdb.europe-west1.firebasedatabase.app/usersData/$userId/userGoals.json');
try {
print(kcal);
final response = await http.post(
url,
body: json.encode(
{
'currentBalance': kcal,
'protein': p,
'carbs': c,
'fats': f,
},
),
);
var decodedData = json.decode(response.body) as Map<String, dynamic>;
print(decodedData['currentBalance']);
if (decodedData['error'] == null) {
balance = decodedData['currentBalance'];
} else {
showDialog(
context: context,
builder: (ctx) => const AlertDialog(
title: Text('An error accured'),
content: Text('Please try again later.'),
),
);
}
notifyListeners();
} catch (err) {
rethrow;
}
}
void didChangeDependencies() {
if (_runsForFirstTime == true) {
setState(() {
_isLoading = true;
});
User? user = FirebaseAuth.instance.currentUser;
Provider.of<RecipeProvider>(context).fetchProducts();
Map<String, dynamic> initialData =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
Provider.of<DiaryProvider>(context, listen: false)
.AddUserGoals(user!.uid, initialData['kcal']!, initialData['p']!,
initialData['c']!, initialData['f']!, context)
.then((_) {
setState(() {
_isLoading = false;
});
});
}
_runsForFirstTime = false;
super.didChangeDependencies();
}
When you call http.post() you tell the REST server to create a new resource (with a unique ID) under the path, so that's what Firebase does.
If you want the server to write the data you pass at the path, use http.put().
Also see:
What is the difference between POST and PUT in HTTP?
I'm trying to set MassTransit up with Azure Service Bus. I want to use the DLQ instead of relying on _skipped and _error queues, but I'm not sure if I'm doing it wrong.
This is a simplified version of my setup:
services.AddMassTransit(busConfigurator =>
{
busConfigurator.AddConsumer<MessageConsumer>();
busConfigurator.UsingAzureServiceBus((context, serviceBusBusFactoryConfigurator) =>
{
serviceBusBusFactoryConfigurator.Host(connectionString);
serviceBusBusFactoryConfigurator.SubscriptionEndpoint<Message>(
"message-subscription",
configurator =>
{
configurator.ConfigureDeadLetterQueueErrorTransport();
configurator.ConfigureDeadLetterQueueDeadLetterTransport();
configurator.ConfigureConsumer<MessageConsumer>(context);
});
});
});
services.AddMassTransitHostedService(true);
---
public class Message
{
public string Text { get; set; }
}
public class MessageConsumer : IConsumer<Message>
{
public Task Consume(ConsumeContext<Message> context)
{
throw new Exception("Hello");
}
}
When publishing a message ( await _bus.Publish<Message>(new {})), I'm getting something like this in the logs:
[15:19:22 | DBG | MassTransit] Create send transport: sb://mytestbus.servicebus.windows.net/MassTransit/Fault--Acme.Common.Messaging.Contracts.Events/Message--
[15:19:22 | DBG | MassTransit] Topic: MassTransit/Fault--Acme.Common.Messaging.Contracts.Events/Message-- ()
[15:19:22 | DBG | MassTransit] Topic: MassTransit/Fault ()
[15:19:22 | DBG | MassTransit] Subscription Fault-MassTransit (MassTransit/Fault--Acme.Common.Messaging.Contracts.Events/Message-- -> sb://mytestbus.servicebus.windows.net/MassTransit/Fault)
[15:19:22 | DBG | MassTransit] SEND sb://mytestbus.servicebus.windows.net/MassTransit/Fault--Acme.Common.Messaging.Contracts.Events/Message-- 09c40000-5dcc-0015-85cf-08d9988339b8 MassTransit.Fault<Acme.Common.Messaging.Contracts.Events.Message>
[15:19:22 | ERR | MassTransit] R-FAULT sb://mytestbus.servicebus.windows.net/Acme.Common.Messaging.Contracts.Events/Message/Subscriptions/message-subscription 09c40000-5dcc-0015-5c52-08d998833786 Acme.Common.Messaging.Contracts.Events.Message Acme.Services.UpdateService.MessageConsumer(00:00:03.0423556)
System.Exception: Hello
at Acme.Services.UpdateService.MessageConsumer.Consume(ConsumeContext`1 context) in C:\Development\acme\src\Acme.Services.UpdateService\Startup.cs:line 503
at MassTransit.Pipeline.Filters.MethodConsumerMessageFilter`2.GreenPipes.IFilter<MassTransit.ConsumerConsumeContext<TConsumer,TMessage>>.Send(ConsumerConsumeContext`2 context, IPipe`1 next)
at GreenPipes.Pipes.LastPipe`1.Send(TContext context)
at MassTransit.Scoping.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next)
at MassTransit.Scoping.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next)
at MassTransit.Pipeline.Filters.ConsumerMessageFilter`2.GreenPipes.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next)
[15:19:23 | WRN | MassTransit] Message Lock Lost: 09c400005dcc00155c5208d998833786
Microsoft.Azure.ServiceBus.MessageLockLostException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue, or was received by a different receiver instance.
at Microsoft.Azure.ServiceBus.Core.MessageReceiver.DisposeMessagesAsync(IEnumerable`1 lockTokens, Outcome outcome)
at Microsoft.Azure.ServiceBus.RetryPolicy.RunOperation(Func`1 operation, TimeSpan operationTimeout)
at Microsoft.Azure.ServiceBus.RetryPolicy.RunOperation(Func`1 operation, TimeSpan operationTimeout)
at Microsoft.Azure.ServiceBus.Core.MessageReceiver.DeadLetterAsync(String lockToken, IDictionary`2 propertiesToModify)
at MassTransit.Azure.ServiceBus.Core.Contexts.ReceiverClientMessageLockContext.DeadLetter(Exception exception)
at MassTransit.Azure.ServiceBus.Core.Pipeline.DeadLetterQueueExceptionFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.GenerateFaultFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at MassTransit.Azure.ServiceBus.Core.Pipeline.DeadLetterQueueExceptionFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.GenerateFaultFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at GreenPipes.Filters.RescueFilter`2.GreenPipes.IFilter<TContext>.Send(TContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.DeadLetterFilter.GreenPipes.IFilter<MassTransit.ReceiveContext>.Send(ReceiveContext context, IPipe`1 next)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Azure.ServiceBus.Core.Transport.BrokeredMessageReceiver.MassTransit.Azure.ServiceBus.Core.Transport.IBrokeredMessageReceiver.Handle(Message message, CancellationToken cancellationToken, Action`1 contextCallback)
[15:19:23 | WRN | MassTransit] Exception on Receiver sb://mytestbus.servicebus.windows.net/Acme.Common.Messaging.Contracts.Events/Message/Subscriptions/message-subscription during UserCallback ActiveDispatchCount(0) ErrorRequiresRecycle(False)
Microsoft.Azure.ServiceBus.MessageLockLostException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue, or was received by a different receiver instance.
at Microsoft.Azure.ServiceBus.Core.MessageReceiver.DisposeMessagesAsync(IEnumerable`1 lockTokens, Outcome outcome)
at Microsoft.Azure.ServiceBus.RetryPolicy.RunOperation(Func`1 operation, TimeSpan operationTimeout)
at Microsoft.Azure.ServiceBus.RetryPolicy.RunOperation(Func`1 operation, TimeSpan operationTimeout)
at Microsoft.Azure.ServiceBus.Core.MessageReceiver.DeadLetterAsync(String lockToken, IDictionary`2 propertiesToModify)
at MassTransit.Azure.ServiceBus.Core.Contexts.ReceiverClientMessageLockContext.DeadLetter(Exception exception)
at MassTransit.Azure.ServiceBus.Core.Pipeline.DeadLetterQueueExceptionFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.GenerateFaultFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at MassTransit.Azure.ServiceBus.Core.Pipeline.DeadLetterQueueExceptionFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.GenerateFaultFilter.GreenPipes.IFilter<MassTransit.ExceptionReceiveContext>.Send(ExceptionReceiveContext context, IPipe`1 next)
at GreenPipes.Filters.RescueFilter`2.GreenPipes.IFilter<TContext>.Send(TContext context, IPipe`1 next)
at MassTransit.Pipeline.Filters.DeadLetterFilter.GreenPipes.IFilter<MassTransit.ReceiveContext>.Send(ReceiveContext context, IPipe`1 next)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock)
at MassTransit.Azure.ServiceBus.Core.Transport.BrokeredMessageReceiver.MassTransit.Azure.ServiceBus.Core.Transport.IBrokeredMessageReceiver.Handle(Message message, CancellationToken cancellationToken, Action`1 contextCallback)
at MassTransit.Azure.ServiceBus.Core.Transport.BrokeredMessageReceiver.MassTransit.Azure.ServiceBus.Core.Transport.IBrokeredMessageReceiver.Handle(Message message, CancellationToken cancellationToken, Action`1 contextCallback)
at MassTransit.Azure.ServiceBus.Core.Contexts.SubscriptionClientContext.<>c__DisplayClass16_0.<<OnMessageAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.Azure.ServiceBus.MessageReceivePump.MessageDispatchTask(Message message)
Is this by design or am I doing something wrong? I would have thought that the message would have been moved to the DLQ, without any logged warnings (except for the exception that I throw).
If I uncomment ConfigureDeadLetterQueueErrorTransport, then I no longer get the MessageLockLostException.
Using MassTransit 7.2.3
With a subscription endpoint, the DLQ is the default behavior.
What you've configured is likely conflicting with the default configuration. If you get rid of the following lines, it should work as expected:
configurator.ConfigureDeadLetterQueueErrorTransport();
configurator.ConfigureDeadLetterQueueDeadLetterTransport();
You can see that those same lines are already configured for subscription endpoints.
Those lines are only needed to configure a receive endpoint to use the DLQ.
So im trying to stream data from firestore but when printing the data I get:
I/flutter ( 8356): Closure: () => Map<String, dynamic> from Function 'data':.
I am using this code to fetch the data:
void messagesStream() async {
Stream collectionStream = _firestore.collection('messages').snapshots();
await for (var snapshot in collectionStream) {
for (var message in snapshot.docs) {
print(message.data());
}
}
When new data is added to the messages collection I get the Closure message so it is interacting with the databse.
What I want is it to print out the contents of the new document within the collection.
Any help is appreciated.
That's not the way you're supposed to iterate the results of a Stream. If you have a Stream and you want to process its results, you're supposed to use listen() to receive the results asynchronously.
Stream collectionStream = _firestore.collection('messages').snapshots();
collectionStream.listen((QuerySnapshot querySnapshot) {
querySnapshot.documents.forEach((document) => print(document.data()));
}
See also: Firestore collection query as stream in flutter
You might also want to review the documentation to learn how to query Firestore in Flutter.
void getMessages() async {
final messages= await _firestore.collection('messages').get();
for(var message in messages.docs){
print(message.data());
}
this is working check this and call getMessages() wherever you wana call
I encountered the same issue with pretty much your exact same code (sans your Stream variable). My suggestion is to delete the Stream var altogether (I tested the code below and got it to print the data from the Firestore database) :
void messagesStream() async {
await for (var snapshot in _firestore.collection('messages').snapshots()) {
for (var message in snapshot.docs) {
print(message.data());
}
}
}
Alternatively, try addding QuerySnapShot as the data type for your Stream variable (untested):
Stream<QuerySnapshot> collectionStream = _firestore.collection('messages').snapshots();
You could also replace the entire method by creating a new Stateless Widget (MessagesStream) that returns a StreamBuilder:
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').snapshots(),
builder: (context, snapshot) {
final messages = snapshot.data.docs;
for (var message in messages) {
print(message.data());
}
...and call it wherever you see fit while you test:
class _ChatScreenState extends State<ChatScreen> { (...)
body: Column(children: <Widget> [
//Just an example.
MessageStream(),
],
),
(...)
}
***Be sure you make the _fireStore (which should be a FirebaseFirestore.instance) a global variable if you're going with the Stateless Widget route.
I received this error while trying to throw a custom error class InkError:
You meed add toMap the class.
final response = await http.post(url, body: body, headers: headers);
final json = jsonDecode(response.body);
if (response.statusCode == HttpStatus.ok) {
return json;
} else {
throw InkError(
code: 0,
message: json['message'],
statusCode: response.statusCode,
).toMap();
InkError
class InkError {
/// Error code.
final int code;
/// Error message.
final String message;
/// HTTP Status Code
final int? statusCode;
const InkError({
required this.code,
required this.message,
this.statusCode,
});
factory InkError.fromJSON(Map<String, dynamic> json) => InkError(
code: json['code'] as int,
message: json['message'] as String,
statusCode: json['statusCode'],
);
Map<String, dynamic> toMap() {
return {
'code': code,
'message': message,
'statusCode': statusCode,
};
}
#override
String toString() {
return toMap().toString();
}
}
What is the difference between:
Future<void>, async, await, then, catchError
Future<void> copyToClipboard(BuildContext context, String text) async {
await Clipboard.setData(ClipboardData(text: text))
.then((_) => showSnackBar(context, 'Copied to clipboard'))
.catchError((Object error) => showSnackBar(context, 'Error $error'));
}
void, async, await, then, catchError
void copyToClipboard(BuildContext context, String text) async {
await Clipboard.setData(ClipboardData(text: text))
.then((_) => showSnackBar(context, 'Copied to clipboard'))
.catchError((Object error) => showSnackBar(context, 'Error $error'));
}
void, then, catchError
void copyToClipboard(BuildContext context, String text) {
Clipboard.setData(ClipboardData(text: text))
.then((_) => showSnackBar(context, 'Copied to clipboard'))
.catchError((Object error) => showSnackBar(context, 'Error $error'));
}
All methods work. If I'm using then and catchError, do I still need to wrap the code in an async function?
Which is the recommended way?
First of, the concept of async/await and then is basically the same. Many people say that async/await is the more elegant way to deal with Promises (because it looks more structured). What both ideas do is: "Do something and once it is done, do something else".
Regarding Future<void> copyToClipboard(BuildContext context, String text) async {...}:
Here you are returning a Promise from your function. Meaning, that you can call the function from somewhere else with
await copyToClipboard(context,"Text");
print("Done");
You can, however also return the Promise itself (and then handle it wherever you call that function):
Future<void> copyToClipboard(BuildContext context, String text) async {
return Clipboard.setData(ClipboardData(text: text));
}
void somewhereElse() async{
await copyToClipboard(context,"Text"); // (1)
print("Copied"); //Happens after (1)
}
So if you are using then, you will put the following instructions into the corresponding function called by it (as seen in your 3rd snippet). Solving the 2nd snippet with async would looke like this:
void copyToClipboard(BuildContext context, String text) async {
await Clipboard.setData(ClipboardData(text: text));// (1)
showSnackBar(context, 'Copied to clipboard')); // (2) This will only be called when (1) has completed
}
If this instruction throws an error, you can wrap it into a try/catch block:
void copyToClipboard(BuildContext context, String text) async {
try{
await Clipboard.setData(ClipboardData(text: text));// (1)
showSnackBar(context, 'Copied to clipboard')); // (2) This will only be called when (1) has completed
}catch (error) {
print(error.message);
}
}
If you prefer then/wait or async/await is up to you. I would recommend async/await. Keep in mind that when using async/await, you need to put the async keyword into the signature of the function, so functions calling this function are aware, that calling it may take a while and wait for it.
I am use Firestore transaction for avoid race condition of add more than 1 user to chat. If transaction timeout, I show error dialog.
But when I move this logic to Model2 with ChangeNotifier (and Provider) it now show error:
Unhandled Exception: A Model2 was used after being disposed. Once you
have called dispose() on a Model2, it can no longer be used.
(No issue with Firestore transaction. This is expected):
PlatformException(9, Transaction failed all retries.: Every document
read in a transaction must also be written in that transaction., null)
It seem ChangeNotifier is call dispose() too early?
This mean it not allow me to handle error of race condition because it is already dispose.
Model:
class Model2 extends ChangeNotifier {
...
bool userAdded;
Future<bool> addUser() async {
try {
await Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot chatRoomDocSnapshot =
await transaction.get(chatRoomDocRef);
bool userInChat = await chatRoomDocSnapshot[‘userInChat'];
if (userInChat == false) {
await transaction.update(chatRoomDocSnapshot.reference, {
‘userInChat': true,
});
}
});
await chatUserRef.setData({
‘User’: user,
});
userAdded = true;
notifyListeners();
} catch (e) {
print(e);
userAdded = false;
notifyListeners();
}
return userAdded;
}
Provider (in stateful widget):
fetchData();
return ChangeNotifierProxyProvider<Model1, Model2>(
initialBuilder: (_) => Model2(),
builder: (_, model1, model2) => model2
..string = model1.string,
),
child: Consumer<Model2>(
builder: (context, model2, _) =>
...
await model2.addUser();
...
Anyone know solution?
Thanks!
Update:
Issue is not throw if I remove fetchData(); from build method before ChangeNotifierProxyProvider .
But why is this fix the issue?
Going through the comments, it's likely that calling fetchData() rebuilds the entire screen - calling the dispose() method. If you're still having issues, I suggest providing logs and the code inside fetchData() for better context.