This Is My FAVORITE Error Handling Class

Поделиться
HTML-код
  • Опубликовано: 26 ноя 2024

Комментарии • 116

  • @mv2e19
    @mv2e19 8 месяцев назад +67

    I agree that using a custom Result with a sealed hierarchy of errors is the way to go. However, I think you should have included the extension functions that really make this class useful. Things like ifSuccess, ifError, getOr, getOrNull, recover, map, and, andAlso, orElse, etc. Michael Bull’s Result class, Arrow’s Either, and Rust’s Result have many more examples for functions that make “railway oriented programming” possible with function chaining.

    • @DiegoNovati1
      @DiegoNovati1 8 месяцев назад

      To avoid all ypur suggested implementations, I use 'arrow-kt' and each use case returns a Either nomad: left contains the failure, right contains the success; the caller uses the 'fold' method to check the left and the right cases. As further details, the 'when' on the left case has to implement an always present case: UnexpectedError. The UnexpectedError is created when the use case detects an unmanaged exception that is automatically converted to the UnexpectedError to avoid the crash of the App without the need of putting every use case inside a try/catch.

    • @mirza5373
      @mirza5373 8 месяцев назад +10

      I think, this goes into infinite creativity. He only explains the bare bone of how the Result class works. You can just add your own functionality that fits your needs

    • @DjuroRadusinovic
      @DjuroRadusinovic 8 месяцев назад +14

      can you point to some articles or courses explaining these please?

    • @mv2e19
      @mv2e19 8 месяцев назад

      @@DjuroRadusinovic checkout Rust’s or Michael Bull’s implementation

  • @skarloti
    @skarloti 8 месяцев назад +6

    Absolutely! The advantage of using a sealed interface instead of strings is huge.👍

  • @baranidharanmurali469
    @baranidharanmurali469 8 месяцев назад +8

    I do this by writing custom Either sealed class left being error and right being the data we don't need the entire arrow library just few of those extensions like fold makes it lot easier. As always good tutorial 👏

    • @nesletchimaew9209
      @nesletchimaew9209 8 месяцев назад +4

      sometimes you don't need data for a success result. sometimes you need data for an error. Also either is totally unreadable

    • @baranidharanmurali469
      @baranidharanmurali469 8 месяцев назад

      ​@@nesletchimaew9209 in those cases u can write other extension functions to just get left or right from either and I use it mostly in network calls which will have both the data and error most of the time and as I mentioned extension functions like fold just has 2 lambdas first being the actual data and second the error I mean I think it is much easier than writing if else or when expression to determine if it is data or error all the time. If u haven't tried the fold or other extensions i request u to try.

    • @ishchuk_
      @ishchuk_ 8 месяцев назад

      @@nesletchimaew9209 ok

    • @JPilsonSumbo
      @JPilsonSumbo 8 месяцев назад +3

      You definitely need a better naming 😢

    • @lordarcadius
      @lordarcadius Месяц назад

      I use the same approach in Flutter

  • @ArthurKhazbs
    @ArthurKhazbs 8 месяцев назад +6

    The idiomatic Kotlin implementation of the classic Result pattern - a wonderful thing to know! Oh, and also - awesome usage of "when subject capture" at 12:10 - I didn't know we could do "when val"!!

  • @gekylafas
    @gekylafas 8 месяцев назад +5

    Mapping 4xx or 5xx errors from retrofit's (or ktor's) exceptions to your own enums may seem redundant to some, but this is the only way to isolate your presentation layer from the actual networking library used by the data layer. Which in turn makes it easy to switch networking libraries if the need arises, e.g. you start using Kotlin Multiplatform (which retrofit does not support).
    Great video, as usual!

    • @kivan26
      @kivan26 8 месяцев назад +2

      You can always extract or abstract away the process of converting http error codes,or onError(Exception) callbacks into your own Error structure, so you don't repeat yourself.

  • @JPilsonSumbo
    @JPilsonSumbo 8 месяцев назад +2

    Furthermore you could map the status code of network request to your enums so that you avoid doing too many "when" . ... Great video btw

  • @زيد_اليماني
    @زيد_اليماني 8 месяцев назад +1

    I had good luck two days ago and I was looking for a solution to this problem and a way to deal with different types of errors, but the matter was terrible, and it happened that today you are posting a video on this topic and delving into it in depth. Thank you.

  • @pelealexandru
    @pelealexandru 8 месяцев назад +3

    Thank you thank you thank you! I saw a very similar video yesterday from Stevdza-San and I pointed out the same concerns you had about localisation and separation of concerns. I was about to write my first Medium article on this but then your video came out with more or less the same solution. Awesome video Philip!

  • @serhiiihnatiev732
    @serhiiihnatiev732 2 месяца назад

    Thanks for the video. In case we want to use strict error type in the custom Result and make it look better we can use the same approach Kotlin result is using. There is no reason to define both generics for data and error.
    sealed class Result {
    data class Success(val data: T) : Result()
    data class Error(val error: DomainError) : Result()
    // your favorite helpers like map, flatMap etc...
    inline fun onSuccess(action: (T) -> Unit): Result {
    if (this is Success) action(data)
    return this
    }
    }

  • @shifthackz
    @shifthackz 8 месяцев назад

    Interesting approach which looks clean, but is there actually a need to introduce another domain layer for errors? You can just use regular kotlin Result class (which also can return non-nullable data), and throw a different instances of custom Throwable classes for different error cases (or even introduce a custom base sealed interface for domain layer throwables), then just catch throwable in a ViewModel and map a different throwables to the resource strings and show different messages? And in my opinion when some domain logic functions throw exceptions makes code more readable and does not need some custom mapping when using it with mapping/flat-mapping with coroutines or rx.

    • @PhilippLackner
      @PhilippLackner  8 месяцев назад +1

      I don't like having to "remember" that I have to catch exceptions and then needing to go to the data layer to see which ones could be thrown. I guess it's a matter of taste in the end, but a wrapper class tells you which error types it could return and forces you to handle them

    • @fardalakter4395
      @fardalakter4395 8 месяцев назад +1

      I use this approach in vm and use generic exception handler and map throwable for possible errors from both remote and local to a string res. It just a matter of single error handler vs modular error handler. The trade off for phillip's approach is that it generating a lot of files and code and complex for beginner-intermediate level devs in trade of clean separated code

  • @jimpauloovejera2599
    @jimpauloovejera2599 8 месяцев назад +1

    instead of passing message: String from your old sample you could just pass in enum class there :D and its gonna be the same but less boilerplate

  • @CryptoCodeZone
    @CryptoCodeZone 8 месяцев назад +2

    Very nice Improvement, Oh boy, much to learn, much to re-factor!

  • @lukesmoljak8617
    @lukesmoljak8617 8 месяцев назад +3

    If you have a use case class that throws its own domain level errors that also communicates with a repository, how would you encapsulate both DataError as well as the usecase's errors? I can see these types of scenarios happening where the usecase returns errors of type Error supertype only. It would be preferable to know the exact subset of errors it could be without looking at the usecase code. Any ideas?

  • @looee4462
    @looee4462 Месяц назад +1

    I have two questions:
    1. Why on this code (7:40) we use "out" keyword for each generic?
    2. Why in Success class we use "" instead of writing "data class Success(val data: D) : Result"?

    • @esekaemmanuel7295
      @esekaemmanuel7295 17 дней назад

      for the answer to your first question its due to covariance. In short that out keyword can help if you have really complex result, the out keyword just helps your code work for any data no matter how complex

  • @ubersticks
    @ubersticks 8 месяцев назад +2

    How do you recommend dealing with "Loading" state? It seems closely related. Solutions often include all-in-one (Success|Error|Loading) or creating a second class that is used in the Presentation layer that includes Loading.

    • @danielayodeji3641
      @danielayodeji3641 8 месяцев назад +5

      That should be handle in your presentation layer

  • @wojtekkrystyniak
    @wojtekkrystyniak 8 месяцев назад +20

    it's better to use Arrow's Either which is pretty much the same thing but additionally provides plenty of utility functions

    • @prashantwosti
      @prashantwosti 8 месяцев назад +8

      If you could achieve it in a simple way, why use third-party libs? I'm not saying using Arrow lib is bad but it could be unnecessary.

    • @Mike-er2ih
      @Mike-er2ih 8 месяцев назад +4

      ​@@prashantwostiThis. I don't like throwing huge libraries into project as a 1st solution

    • @ericksli
      @ericksli 8 месяцев назад +4

      @@prashantwosti You can archieve it by writing custom code, but you have to write unit test cases and extension functions by yourself
      Also, for a project that have different collaborators, it would be better to stick with some de facto libraries to save the onboarding time

    • @johngray2875
      @johngray2875 8 месяцев назад +1

      Yup, I just started using arrow-kt about a month ago, and I love it.

  • @RonnyBubke
    @RonnyBubke 8 месяцев назад +1

    What you really need is only the data and catching exceptions on the presentation layer. For general errors just use an error handler which translates the error into an action or a message. That's it.

    • @PhilippLackner
      @PhilippLackner  8 месяцев назад +1

      Don't like having to remember to catch all types of exceptions and always needing to look which ones could be thrown. A wrapper class tells you all different error types that could be relevant in a specific use case and you can decide which ones to handle

    • @RonnyBubke
      @RonnyBubke 8 месяцев назад

      @@PhilippLackner You don't need to remember all exceptions when you have Tests.

    • @RonnyBubke
      @RonnyBubke 8 месяцев назад

      @@PhilippLackner You can simple catch all exceptions and then have an global error handler wich can be extend for custom handlings. The advantage is you can handle the value directly and don't need to look if it wasn't a success or not, the success is the default and errorhandling is the exception.

    • @ArthurKhazbs
      @ArthurKhazbs 8 месяцев назад +1

      Thrown exceptions are more difficult to click and read through than returned failure objects, especially when done across architecture layers

  • @robchr
    @robchr 8 месяцев назад +4

    I see wrapped errors a lot and it makes processing the successful path require a flatMap. I prefer to throw errors and have the root coroutine catch and handle the error. This leads to much cleaner data, domain layers since they do not need to handle errors and only the coroutine in the ViewModel is concerned with errors.

    • @PhilippLackner
      @PhilippLackner  8 месяцев назад +4

      I don't like this too much since you then always need to look back at the data layer to know which types of errors/exception could be thrown. There's also nothing that forces you to catch these, so it's easy to forget

    • @mv2e19
      @mv2e19 8 месяцев назад +1

      I think one of the biggest reasons not to use exceptions for domain-specific stuff like this is exhaustiveness. If you use typed-errors, you can then create an error hierarchy with a sealed class, and you can guarantee at compile-time that every possible domain error is handled

    • @5erTurbo
      @5erTurbo 8 месяцев назад

      I agree with @robchr. I personally prefer exceptions, because you know they will cancel code execution, if something goes wrong. And exceptions also provide a useful stacktrace - result classes (mondas) don't.
      Using a monad is not a silver bullet. You can call a function and forget to actually use Result return value. Monads force you to deal with wrapper object on every step of the way - you can't propagate errors between function, like you can exceptions.

  • @nsshurtz
    @nsshurtz 8 месяцев назад +2

    I'd recommend that the Succes result not take an E type (it doesn't care about the error) and have it use Nothing as it's error type for implementing the interface. Same thing goes for the D type on the Error implementation.

    • @PhilippLackner
      @PhilippLackner  8 месяцев назад

      That makes it hard to write utility functions for the Result class, since they need to extend it with both generic params

    • @kacetal
      @kacetal 8 месяцев назад

      ​@@PhilippLacknerTo make writing easier, you can use Kotlin contracts.

    • @StreetsOfBoston
      @StreetsOfBoston 8 месяцев назад

      ​@@PhilippLackner That should not be an issue, since both type parameters of Result are covariant and "Nothing" is a sub-class of all types.
      I.e. If you'd put Nothing for E type in Success and Nothing for D type in Error, then `fun doSomeUtilThing(r: Result...)` can be called with with a Success (Result) or an Error (Result).

  • @UsmonWasTaken
    @UsmonWasTaken 8 месяцев назад +7

    Just use the Arrow library, instead of reinventing the wheel. You can even just copy the Either class from their git repo if you don't want the whole library :)

    • @gorudonu
      @gorudonu 8 месяцев назад +1

      This

    • @javiere.gonzalez1021
      @javiere.gonzalez1021 7 месяцев назад

      I would but Arrow's Left/Right nomenclature sucks and gives off code stink

  • @leunamvon2565
    @leunamvon2565 7 месяцев назад

    Philipp has so good and solid approaches that I implement them also in iOS.

  • @TheMikkelet
    @TheMikkelet 8 месяцев назад +1

    This seems overly complicated when you can just throw custom errors (NotFoundException, TimeoutException) and also use the *built in* coroutine function runCatching

  • @HuteriManza
    @HuteriManza 6 месяцев назад

    I would say this is simpified version, in practices, there are more than this, and usually we will have common error handling to handle cases such as unauthorised token, internal server error, generic error message, and we would not want to handle this in every api call and this things require strategy to reduce boilerplate.

  • @renvzla
    @renvzla 4 месяца назад

    This is a very nice video. Lots of important info. Thanks Philipp.

  • @davidgaier7324
    @davidgaier7324 7 месяцев назад +1

    Instead of naming the data class error of result interface, we could name it Failure like scala did and don't have collision with error sealed interface

  • @Ayor88
    @Ayor88 8 месяцев назад

    what about the "class Loading(data: T? = null): Ressource(data)" you had in your Ressource class before ? Would you still have a similar mechanism in the Result interface or do you manage it in an another way ?

  • @mobAppAR
    @mobAppAR 8 месяцев назад +1

    Why don't we inject context using hilt?

  • @abdussamad833
    @abdussamad833 8 месяцев назад +1

    Great way to handling errors, But how can we use it for multimodule architectures? could you please help to me?

  • @coldwised
    @coldwised 8 месяцев назад +2

    But UiText is not Serializable, how do we save state?

  • @hoeric5605
    @hoeric5605 8 месяцев назад

    Thanks for philipp, i've learn a useful and efficient way to write error class, and do error handling. It would be easier to write out logic codes ✌😁

  • @nilaxgajjar.work8
    @nilaxgajjar.work8 6 месяцев назад

    Hi @PhilippLackner,
    Thanks for explaining complex scenarios and concepts in such a simple way. I have a question about UserEvent: Should we create a separate UserEvent for each ViewModel, or is there a more generic approach you would recommend?

  • @isolino.ferreira
    @isolino.ferreira 8 месяцев назад +1

    Great video!
    How about mapping data error into custom domain exceptions?
    I used to inject context into repositoryImpl, them throwing custom exceptions with the right string. If the erro message comes from backend, I just throw the custom exception with that message.

    • @PhilippLackner
      @PhilippLackner  8 месяцев назад

      Below some other comments I explained why I'm not too big of a fan for using exceptions for ALL errors

  • @technophile_
    @technophile_ 8 месяцев назад

    I've not watched the full video yet, but it looks like this approach is very similar to how errors are handled in swift

  • @davidsantiagorodriguezcruz7023
    @davidsantiagorodriguezcruz7023 29 дней назад

    its nice, but i have a question, how can i get information that send the backend in case of error?. for example i have to make a different data processing depending the data that send the backend

  • @McRookworst
    @McRookworst 8 месяцев назад +5

    Isn't this just what exceptions are for? Executing use cases in a runCatching block is more concise and standard than all this boilerplate. I understand that some might dislike working with exceptions, but you should at least suggest the possibility.

  • @asuras2891
    @asuras2891 8 месяцев назад

    While I like the approach in general, I couldnt figure out yet how to use this on server side. Like when I have a ktor backend and I use such an error handling, I want to send the result back to the client via rest or websockets so my client can show the result or the error. Since we have a sealed data structure with generics trying to serialize them with kotlinx-serialization-json turned out to be a nightmare. Type erasure kicks in hard here. You can package a lego star wars set in the backend and unwrap a lego harry potter set in the frontend - not sure if lightsabers stand a chance vs magicians!

  • @LightDante
    @LightDante 11 дней назад

    Can you explain how to use in and out modifier?

  • @kareemmohamed5589
    @kareemmohamed5589 3 месяца назад

    I can return a specific error string in the Resource class and check the error string in the ViewModel, just like what was done with the Result class. and how can I add a Loading state in the Result class?

  • @Rane-j8h
    @Rane-j8h 3 месяца назад

    Can you plz make video when to use Result class provided by Kotlin and the approach you have specified here?

  • @harishpadmanabh6857
    @harishpadmanabh6857 8 месяцев назад

    How to handle the scenario when we need to show the message in response as error message and not the string from resources?

  • @roushanali1918
    @roushanali1918 8 месяцев назад

    very logical, great explanation

  • @malikkissoum730
    @malikkissoum730 7 месяцев назад

    How to deal with the case I want to show different pieces of UI depending on what error occurred, not just show a message, I want to show a floating smiley face when there is a network error and a blinking button when there is a password error

  • @josephofem5448
    @josephofem5448 7 месяцев назад

    How can you handle a network request that returns its own custom error message. Say in your example, the provided password is wrong and the error message communicates that.

  • @petibah27
    @petibah27 8 месяцев назад

    Hi, Philipp can you make a projet where you use koin and ktor please

  • @SalVic-l7b
    @SalVic-l7b 8 месяцев назад +1

    Help me understand this. I see we’re mapping the server error code to defined error type without considering the actual error message returned from the server.
    What if we want to show the actual error message returned from the server?

    • @PhilippLackner
      @PhilippLackner  8 месяцев назад +1

      Then you need to extend this with a separate string field, but that wouldn't support localization

  • @mohannadyoussef8683
    @mohannadyoussef8683 8 месяцев назад

    Nice explanation, Thank you

  • @aabhishek4911
    @aabhishek4911 8 месяцев назад +2

    What will happen when sealed interface Error and PasswordError are in different modules ?

    • @Tuligarnio
      @Tuligarnio 8 месяцев назад

      I think that's probably a mistake, I don't think it should really be sealed. Also, I don't see what's the purpose of defining such interface, it should work the same if you get rid of it.
      Finally adding the generic type of data to the error type and the error type to the data does not seem to be necessary for me.

    • @송규빈-d9z
      @송규빈-d9z 2 месяца назад

      @aabhishek4911
      I think it's better to intentionally use a sealed interface.
      Due to the nature of sealed, it seems that compilation errors can be managed by creating a common module called `model`, and wouldn't it be easier to identify the type of error by using `sealed` to prevent external extension?

  • @coldwised
    @coldwised 8 месяцев назад +1

    OOP is killing me 💀

  • @MD90X
    @MD90X 8 месяцев назад +1

    Is there a book or a tutorial you would recommend for learning kotlin multiplatform ?

    • @Tuligarnio
      @Tuligarnio 8 месяцев назад

      He has a KMM premium course.

  • @abv99997
    @abv99997 8 месяцев назад

    Great video, thank you a lot!

  • @pelealexandru
    @pelealexandru 8 месяцев назад +1

    As an alternative to UiText, is it a bad practice to inject the application context in the ViewModel and then use that to get string resources?

    • @germenwong
      @germenwong 8 месяцев назад

      not good

    • @sevbanthebuyer
      @sevbanthebuyer 8 месяцев назад +1

      yes definitely. because context objects are expensive and your viewmodels lives longer than your screens. you will end up having large memory usage

    • @pelealexandru
      @pelealexandru 8 месяцев назад

      hi @@sevbanthebuyer, thanks for your reply. to my knowledge, the application context is a singleton which is already there in the memory anyway, so just injecting a reference to it in the ViewModel wouldn't have any impact on performance what so ever. please correct me if i'm wrong though

    • @pelealexandru
      @pelealexandru 8 месяцев назад

      hey@@germenwong! can you elaborate?

    • @송규빈-d9z
      @송규빈-d9z 2 месяца назад

      @@pelealexandru
      Let me explain for you: If you just look at performance, your opinion is not wrong.
      However, I think a better direction is to consider various aspects rather than just pursuing performance.
      Not using the applicationContext to manage string resources in the view model also has unit testing implications.
      If you use application in the view model, the code becomes platform-dependent, so there may be difficulties such as mocking when performing unit tests.

  • @dleonardo3238
    @dleonardo3238 8 месяцев назад +4

    You and stevdza competing lmao? Same day same video lol

    • @fardalakter4395
      @fardalakter4395 8 месяцев назад +1

      Lol, happy for that, love both of them

  • @AndrewKupreychik
    @AndrewKupreychik 8 месяцев назад

    Good content I'm looking for!

  • @MrVipulLal
    @MrVipulLal 8 месяцев назад

    Great video, as usual.

  • @justmeagain9302
    @justmeagain9302 4 месяца назад

    Now i just realized you can create extension functions with subclasses😂

  • @enossalar8837
    @enossalar8837 4 месяца назад

    Thanks❤

  • @mustafaammar551
    @mustafaammar551 8 месяцев назад

    thank you bro

  • @heshamabdo6024
    @heshamabdo6024 6 месяцев назад

    what if i want to return Error as String ??

  • @Daaaaaaavid
    @Daaaaaaavid 8 месяцев назад +2

    I prefer Google's AndroidNow approach using UIStates.

  • @mihies
    @mihies 8 месяцев назад

    Any particular reason for using data class for UiText.DynamicString and class for UiText.StringResource?

    • @ArthurKhazbs
      @ArthurKhazbs 8 месяцев назад

      My guess is for making them automatically equatable by value, just in case

    • @mihies
      @mihies 8 месяцев назад

      ​@@ArthurKhazbs Yes, but why only one of the two?

  • @lhk9805
    @lhk9805 8 месяцев назад

    Awesome😊

  • @ubersticks
    @ubersticks 8 месяцев назад

    at 07:19 it is still confusing to me why the "typealias RootError = Error" is helpful. Can you explain this a bit more?

    • @abdelrahmankhaled7575
      @abdelrahmankhaled7575 8 месяцев назад +1

      because he define a new class inside the result sealed class which has name of "Error" , so to not conflict with the other "Error" sealed interface class , he used the typealias to make the sealed interface class take a name of "RootError" instead of "Error" only inside this file

  • @makix08
    @makix08 8 месяцев назад

    Yes I like it !

  • @csabasemler6806
    @csabasemler6806 8 месяцев назад +1

    Thanks for the video Philipp!
    I have a question that has been bothering me for a long time. Why are you talking so fast? In this way, it is difficult to understand the essence of your video. I have come across this many times.
    (Or am I just slow? :-O)

  • @sahilsapehia4789
    @sahilsapehia4789 8 месяцев назад +1

    1st one to comment