Error trapping with Try/Catch

HotShoe

Well-Known Member
Licensed User
Longtime User
The Try/Catch combination is important to understand and use in your programs. Error detection and correction is the mark of good programming. It is not glamorous or even fun, but it is very necessary if you want to build a reputation for reliable, high quality apps and programs. The basic layout of a Try/Catch block looks like this :

B4X:
Try
...normal code to run goes in here.
Catch
...code to run if an error occurs goes here.
End Try

The way this block works is pretty simple and straight forward. The Try keyword tells B4X that an error trapping section is beginning here. All code between the Try and the Catch runs normally unless an error occurs inside THIS block of the app or program. If an error does occur, all processing after the Try keyword STOPS and the Catch part of the block is immediately run.

You can run almost anything you like inside the Catch part of the block, but it should be geared toward dealing with the reason the error was raised. Sometimes you expect an error to happen and the alternative code is in your Catch block like this :

B4X:
Sub getcfg
   Dim cfile As RandomAccessFile

   cfile.Initialize(File.DirInternal, "ADT.cfg", False)

   Try
   crec = cfile.ReadObject(0)
   Catch
   crec.dircolor = Colors.Yellow
   crec.filebg = Colors.Black
   crec.filefg = Colors.White
   crec.linkcolor = Colors.Green
   crec.markbg = Colors.Yellow
   crec.markedcolor = Colors.Magenta
   crec.markfg = Colors.Black
   crec.startdir = curdir
   crec.infolblbg = Colors.White
   crec.infolblfg = Colors.Black
   crec.hidden = False

   savecfg
   End Try

   cfile.Close

End Sub

The above sub expects there to be an error when the app is first installed. In that case, there will not be an ADT.cfg file since no configuration has ever been saved. When that happens, the Catch part of the block is run and default values are assigned to the config record type and then saved in the call to the savecfg sub.

The Try/Catch below monitors a database transaction :

B4X:
Sub Update_eng
   screen_to_erec 'assigns user input to the custom type erec.

   If erec.vtech Then
   vr= 1
   Else
   vr= 0
   End If

   If erec.ohc Then
   ohc= 1
   Else
   ohc= 0
   End If

   state1= "Set ftype="&erec.fueltyp&",name='"&engname.Text&"',desc='"&engdesc.Text&"',cyl="&erec.cyl&",bore="&erec.bore&",stroke="&erec.stroke&",hgv="&erec.hgv&",ccv="&erec.ccv&",dish="&erec.dish&",ivdia="&erec.idia&",evdia="&erec.edia&",rocker="&erec.rock&",p2d="&erec.p2d&",vcyl="&erec.vcyl&",ecm="&erec.ecm&",intake="&erec.itype&",exhaust="&erec.extype&",heads="&erec.head&",nos="&erec.no2&",ltype="&erec.ltype&",spring="&erec.spress&","
   state2= "ilift="&erec.ilift&",elift="&erec.elift&",idur="&erec.idur&",edur="&erec.edur&",icenter="&erec.icenter&",ecenter="&erec.ecenter&",boost="&erec.boost&",ccfm="&erec.blowcfm&",rods="&erec.rod&",balance="&erec.bal&",ign="&erec.ign&",blower="&erec.blower&",rodlength="&erec.rodlen&",fuelsys="&erec.carbtype&",vtec="&vr&"ohc="&ohc

   Try
   db.ExecNonQuery("Update engines " & state1 & state2 & " Where id=" & cur_rec)
   ToastMessageShow("Record updated",True)
   Catch
   Msgbox("The following error occurred : "&LastException.Message,"Error")
   End Try

   do_eng
   dbpnl.Visible = False
     dbpnl.SendToBack

   newrecord= False
   engsavebtn.Enabled= False
   hidedbscreen
   inengdb = False

   show_main
End Sub

The code above attempts to update an sqlite db record. If all goes well, it displays a toast message saying that the record was saved, if not, it displays an error message in a msgbox. It does not take any corrective action, it just displays the error and then returns to the engine database input screen.

Sometimes you want the app to notify you (the developer) in case of an error. In important operations that are critical to the function of the app, I use the sub below to give the user the choice of sending me an email containing the error that occurred and the device that it was running on at the time.

B4X:
Sub ReportError(pName As String, Ver As String, inetAddress As String)
  Dim ex As ExceptionEx 'from the Threading library
  Dim error,ptype As String 'the error string and phone type info
  Dim mail As Email 'built in email type from Phone library

  ex.Initialize(LastException.Message)

  ptype = ph.Manufacturer & " " & ph.Model & CRLF & pName & " version " & Ver & CRLF 'ph is of type Phone from the Phone library.
  error = ptype & CRLF & ex.ToString & CRLF & CRLF & ex.StackTrace & CRLF

  If Msgbox2("An error has occurred. The error is : "&LastException.Message&". Please report this error below.","ERROR","Report","","Dismiss",Null) = DialogResponse.POSITIVE Then
  mail.Attachments.Clear
  mail.Subject= pName & " error" 'Example: pname = “Virtual Dyno”
  mail.To.Add(inetAddress)

  mail.Body= error
  StartActivity(mail.GetIntent) 'calls the intent to show the email client
  End If

End Sub

The reporterror sub above is called inside the Catch block and displays a msgbox that allows the user to either send me an error report or dismiss the error and continue. The sub does not hide anything from the user, it calls their email client and allows them to read the entire error message and requires them to press the Send button. Then the app attempts to deal with the error condition and continues to run normally (well, hopefully anyway).

An example of when I use this is when critical calculations are being made or something is required to happen before the app can continue reliably. For example, I error check all EditTexts that a user can modify, but sometimes, (especially after a new update with lots of changes) I forget to check a new item for errors. If a routine expects a number and the value of an EditText (or any other type) is empty or is a string and not a number, an error is thrown.

Here is a real usage of this type of error reporting from the Catch clause from my Virtual Dyno app :

B4X:
Sub db_to_erec

Try

  dbcur.Position= cur_pos

  dbname= u.proper(dbcur.GetString("name"))
  dbdesc= u.proper(dbcur.GetString("desc"))

  erec.cyl= dbcur.Getint("cyl")
  erec.bore= dbcur.GetDouble("bore")
  erec.stroke= dbcur.GetDouble("stroke")
  erec.hgv= dbcur.GetDouble("hgv")
  erec.p2d= dbcur.GetDouble("p2d")
  erec.ccv= dbcur.GetDouble("ccv")
  erec.dish= dbcur.GetInt("dish")
  erec.vcyl= dbcur.GetInt("vcyl")
  erec.idia= dbcur.GetDouble("ivdia")
  erec.edia= dbcur.GetDouble("evdia")
  erec.ilift= dbcur.GetDouble("ilift")
  erec.elift= dbcur.GetDouble("elift")
  erec.idur= dbcur.GetInt("idur")
  erec.edur= dbcur.GetInt("edur")
  erec.icenter= dbcur.GetInt("icenter")
  erec.ecenter= dbcur.GetInt("ecenter")
  erec.vtech= dbcur.GetInt("vtec") > 0
  erec.ltype= dbcur.GetInt("ltype")
  erec.rock= dbcur.GetDouble("rocker")
  erec.spress= dbcur.GetInt("spring")
  erec.extype= dbcur.GetInt("exhaust")
  erec.head= dbcur.GetInt("heads")
  erec.rod= dbcur.GetInt("rods")
  erec.bal= dbcur.GetInt("balance")
  erec.ign= dbcur.GetInt("ign")
  erec.itype= dbcur.GetInt("intake")
  erec.no2= dbcur.GetInt("nos")
  erec.boost= dbcur.GetInt("boost")
  erec.rock= dbcur.GetDouble("rocker")
  erec.blower= dbcur.GetInt("blower")
  erec.blowcfm= dbcur.GetInt("ccfm")
  erec.rodlen= dbcur.GetDouble("rodlength")
  erec.fueltyp= dbcur.GetInt("ftype")
  erec.carbtype= dbcur.GetInt("fuelsys")
  erec.ohc = dbcur.GetInt("ohc") > 0
  erec.ecm= dbcur.GetInt("ecm")

  erec_to_vrec 'converts eng db record to a vehicle record

Catch
reporterror
Init_All

End Try

dbcur.Close

End Sub

The sub above needs to succeed in getting a database record loaded correctly into the custom type erec before the app can calculate the results and display reliable values. The sub that actually does the calculations is also enclosed in a Try/Catch block to ensure that errors are trapped and then all values are reset to known defaults if an error occurs.

The Try/Catch block allows you to save face to your users in the event an error occurs. It looks more professional to tell the user "Yeah I know an error occurred and I want to know why and where." instead of something like "An error has occurred and this app will be closed", or you can handle the error silently and put the app into a condition that it can continue running reliably.

If you want to know why some apps get tens of millions of downloads and consistently high ratings, it's because they do what they are supposed to do without crashing in an ugly, gory manner. Error checking and correcting is just part of the game in professional programming, regardless of whether it is a free or paid app. Check user inputs and especially any file operations done in your apps or programs and the world will be a much happier place.

I know that sounds a bit like I'm preaching, but it is one of my pet peeves that many if not all new programmers do. They get the guts in the app to make it run, but leave out the tedious part of going through all of the possible problems that can come up. Learning to think like a machine is a very handy talent to acquire in the programming world, and it just takes time and experience to get there. The effort is well worth your time though.

--- Jem
 
Last edited:

Cableguy

Expert
Licensed User
Longtime User
Hi guys....
I thought of posting here instead of Q&A section, as it can help other understand, that like me, had never really used a try:catch block before...

I have this code:
B4X:
    Dim durationofmonth As Long 
        For n = 27 To 31
            Try
                durationofmonth = DateTime.GetDayOfMonth(DateTime.DateParse(n & "/" & DateTime.GetMonth(ticks) & "/" & DateTime.GetYear(ticks)))
            Catch
                Log(LastException)
            End Try
        Next

My basic thought was, in a for/next, to increase the testing date by one day until it errors out, thus finding the duration of the given month ( the mont is passed to the sub in ticks).
It works quite nicely in release mode, and the execution is not stopped, BUT, in debug, the exception stops the execution... I thought the debug mode would manage the try/catch and let the exception be dealt by the code without stopping...
Is this the normal debug behaviour for a try/catch?


[EDIT] when using a try/catch inside a loop, make sure to exit the loop on the catch section or the exception will halt execution!

[EDIT 2] I can't get the exception to be ignored by the debugger, even after exiting the loop in the catch section, any thoughts?
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
This works for me:
B4X:
    DateTime.DateFormat = "dd/MM/yyyy"
    Dim Ticks As Long = DateTime.DateParse("27/02/2015")
    Dim durationofmonth As Long
    For n = 27 To 31
        Try
            durationofmonth = DateTime.GetDayOfMonth(DateTime.DateParse(n & "/" & DateTime.GetMonth(Ticks) & "/" & DateTime.GetYear(Ticks)))
        Catch
            Log(LastException)
            Exit
        End Try
    Next
    Log(durationofmonth)
 

stevel05

Expert
Licensed User
Longtime User
This also works and uses less processing if you can format the date differently:

B4X:
    DateTime.DateFormat = "dd/MM/yyyy"
    Dim Day As String
    Dim Month As String = "02"
    Dim Year As String = "2015"
    For n = 27 To 31
        Day = n
        Try
            DateTime.DateParse(Day & "/" & Month & "/" & Year)
        Catch
            Log(LastException)
            Day = n-1
            Exit
        End Try
    Next
    Log("Last valid day = " & Day)

They both fail if you change the code and don't clean the project before running again in the rapid debugger.
 
Last edited:

Cableguy

Expert
Licensed User
Longtime User
I may not have been clear making my point....
The routine works as expected, and I retrieve the correct durationofmonth value.
My issue is, why, in debug mode, the execution halts when the exception is caught?
I will try to clean the project and see how it goes...

[EDIT] Cleaning the project solved my problem, the loop exits when the exception is catched and thus I get the correct value for my variable
 

PCowling

Member
Licensed User
Longtime User
Thanks for the mini tutorial. This is an important area of teaching and it really helps. Thank you.
 
Top