[{"data":1,"prerenderedAt":15525},["ShallowReactive",2],{"Categories":3,"Series":203,"NavIndexCategoriesCountFooter":15524},[4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,68,70,71,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202],{"category":5},"System Administration",{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},"Software Development",{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":27},{"category":27},{"category":67},"Drones & RC",{"category":69},"DIY Projects",{"category":67},{"category":72},"Photography",{"category":69},{"category":69},{"category":69},{"category":67},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":67},{"category":69},{"category":69},{"category":67},{"category":67},{"category":72},{"category":72},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":5},{"category":5},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":5},{"category":67},{"category":67},{"category":72},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":72},{"category":67},{"category":138},"3D Printing - Laser Cutting - CNC",{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":5},{"category":138},{"category":27},{"category":27},{"category":138},{"category":138},{"category":72},{"category":158},"Photography,3D Printing - Laser Cutting - CNC",{"category":27},{"category":27},{"category":69},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":67},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":27},{"category":69},{"category":27},{"category":27},{"category":27},{"category":27},{"category":181},"Home Assistant",{"category":181},{"category":72},{"category":27},{"category":27},{"category":72},{"category":138},{"category":5},{"category":72},{"category":72},{"category":138},{"category":27},{"category":181},{"category":181},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},[204,243,276,319,378,409,432,508,538,576,612,645,690,724,752,809,862,910,975,1017,1051,1109,1175,1209,1233,1348,2089,3555,5241,6705,7714,7901,7983,8082,8137,8186,8336,8525,9513,10924,11833,12907,15156],{"id":205,"title":206,"body":207,"category":69,"date":230,"description":213,"embedImage":231,"extension":232,"image":220,"intro":231,"meta":233,"navigation":234,"path":235,"seo":236,"series":219,"sitemap":237,"stem":238,"tags":239,"__hash__":242},"content\u002F2013\u002F11\u002F24\u002Fbathroom-ready-for-replacement.md","Bathroom ready for replacement",{"type":208,"value":209,"toc":226},"minimark",[210,214,221],[211,212,213],"p",{},"Builders, plumbers, electricians and decorators expected tomorrow - bathroom ready to receive them.",[211,215,216],{},[217,218],"img",{"alt":219,"src":220},"Bathroom","\u002Fimages\u002Fposts\u002F2013\u002F11\u002F24-bathroom-01.jpg",[211,222,223],{},[217,224],{"alt":219,"src":225},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F24-bathroom-02.jpg",{"title":227,"searchDepth":228,"depth":228,"links":229},"",2,[],"2013-11-24 12:00 +0100",null,"md",{},true,"\u002F2013\u002F11\u002F24\u002Fbathroom-ready-for-replacement",{"title":206,"description":213},{"loc":235},"2013\u002F11\u002F24\u002Fbathroom-ready-for-replacement",[240,241],"bathroom","diy","4QIL_s_zcGCXDZbGh95V39Bao2v-a_FDvq_TKZOWdPo",{"id":244,"title":245,"body":246,"category":69,"date":268,"description":250,"embedImage":231,"extension":232,"image":255,"intro":231,"meta":269,"navigation":234,"path":270,"seo":271,"series":219,"sitemap":272,"stem":273,"tags":274,"__hash__":275},"content\u002F2013\u002F11\u002F26\u002Fbathroom-end-of-day-2.md","Bathroom end of day 2",{"type":208,"value":247,"toc":266},[248,251,256,261],[211,249,250],{},"Most of the washbasin got unmounted on monday. Not a lot happened on tuesday. State of play - end of day 2:",[211,252,253],{},[217,254],{"alt":219,"src":255},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F26-bathroom-01.jpg",[211,257,258],{},[217,259],{"alt":219,"src":260},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F26-bathroom-02.jpg",[211,262,263],{},[217,264],{"alt":219,"src":265},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F26-bathroom-03.jpg",{"title":227,"searchDepth":228,"depth":228,"links":267},[],"2013-11-26 02:00 +0200",{},"\u002F2013\u002F11\u002F26\u002Fbathroom-end-of-day-2",{"title":245,"description":250},{"loc":270},"2013\u002F11\u002F26\u002Fbathroom-end-of-day-2",[240,241],"njNkAIj63oHegDMkrn42r7Hdmg94Zgc1n6CfjePL1ms",{"id":277,"title":278,"body":279,"category":69,"date":311,"description":283,"embedImage":231,"extension":232,"image":288,"intro":231,"meta":312,"navigation":234,"path":313,"seo":314,"series":219,"sitemap":315,"stem":316,"tags":317,"__hash__":318},"content\u002F2013\u002F11\u002F27\u002Fbathroom-end-of-day-3.md","Bathroom end of day 3",{"type":208,"value":280,"toc":309},[281,284,289,294,299,304],[211,282,283],{},"The wrecker's been :) Quite a difference now - and we now know what we suspected before - that the wall behind the bath itself wasn't dry. Seems to be only wet - not rotten though. More removal tomorrow and apparently some plumbing will start too.",[211,285,286],{},[217,287],{"alt":219,"src":288},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F27-bathroom-01.jpg",[211,290,291],{},[217,292],{"alt":219,"src":293},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F27-bathroom-02.jpg",[211,295,296],{},[217,297],{"alt":219,"src":298},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F27-bathroom-03.jpg",[211,300,301],{},[217,302],{"alt":219,"src":303},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F27-bathroom-04.jpg",[211,305,306],{},[217,307],{"alt":219,"src":308},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F27-bathroom-05.jpg",{"title":227,"searchDepth":228,"depth":228,"links":310},[],"2013-11-27 21:53 +0100",{},"\u002F2013\u002F11\u002F27\u002Fbathroom-end-of-day-3",{"title":278,"description":283},{"loc":313},"2013\u002F11\u002F27\u002Fbathroom-end-of-day-3",[240,241],"T1zYUnImfpPqZbmBMrsx4dn5TGpjLY7XDS-ky_uLJbI",{"id":320,"title":321,"body":322,"category":69,"date":370,"description":326,"embedImage":231,"extension":232,"image":337,"intro":231,"meta":371,"navigation":234,"path":372,"seo":373,"series":219,"sitemap":374,"stem":375,"tags":376,"__hash__":377},"content\u002F2013\u002F11\u002F28\u002Fbathroom-end-of-day-4.md","Bathroom end of day 4",{"type":208,"value":323,"toc":368},[324,327,330,333,338,343,348,353,358,363],[211,325,326],{},"More stuff removed, new shower drain in place - and the lowest level of the floor mounted.",[211,328,329],{},"Tomorrow they're starting with the membrane and the plumber will start putting down some of the pipes.",[211,331,332],{},"State of play - end of day 4:",[211,334,335],{},[217,336],{"alt":219,"src":337},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-01.jpg",[211,339,340],{},[217,341],{"alt":219,"src":342},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-02.jpg",[211,344,345],{},[217,346],{"alt":219,"src":347},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-03.jpg",[211,349,350],{},[217,351],{"alt":219,"src":352},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-04.jpg",[211,354,355],{},[217,356],{"alt":219,"src":357},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-05.jpg",[211,359,360],{},[217,361],{"alt":219,"src":362},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-06.jpg",[211,364,365],{},[217,366],{"alt":219,"src":367},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F28-bathroom-07.jpg",{"title":227,"searchDepth":228,"depth":228,"links":369},[],"2013-11-28 20:49 +0100",{},"\u002F2013\u002F11\u002F28\u002Fbathroom-end-of-day-4",{"title":321,"description":326},{"loc":372},"2013\u002F11\u002F28\u002Fbathroom-end-of-day-4",[240,241],"z_9dcx5S9lNX5PdDOlGOjb97QLSNoflKUiTFrJARHbw",{"id":379,"title":380,"body":381,"category":69,"date":401,"description":385,"embedImage":231,"extension":232,"image":393,"intro":231,"meta":402,"navigation":234,"path":403,"seo":404,"series":219,"sitemap":405,"stem":406,"tags":407,"__hash__":408},"content\u002F2013\u002F11\u002F29\u002Fbathroom-end-of-day-5.md","Bathroom end of day 5",{"type":208,"value":382,"toc":399},[383,386,389,394],[211,384,385],{},"Floor membrane in place - some protective panels down on that and a metal grid starting to go down.",[211,387,388],{},"More of the drain in place too with pipes for sink and bath.",[211,390,391],{},[217,392],{"alt":219,"src":393},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F29-bathroom-01.jpg",[211,395,396],{},[217,397],{"alt":219,"src":398},"\u002Fimages\u002Fposts\u002F2013\u002F11\u002F29-bathroom-02.jpg",{"title":227,"searchDepth":228,"depth":228,"links":400},[],"2013-11-29 21:19 +0100",{},"\u002F2013\u002F11\u002F29\u002Fbathroom-end-of-day-5",{"title":380,"description":385},{"loc":403},"2013\u002F11\u002F29\u002Fbathroom-end-of-day-5",[240,241],"Eu5UNz4dQ3It7volkPnJyhiy_t_q2sRxu--kSp11N7I",{"id":410,"title":411,"body":412,"category":69,"date":424,"description":416,"embedImage":231,"extension":232,"image":421,"intro":231,"meta":425,"navigation":234,"path":426,"seo":427,"series":219,"sitemap":428,"stem":429,"tags":430,"__hash__":431},"content\u002F2013\u002F12\u002F02\u002Fbathroom-end-of-day-8.md","Bathroom end of day 8",{"type":208,"value":413,"toc":422},[414,417],[211,415,416],{},"Weekend over (no work) and today the floor heating cables have been put down and the cement (or whatever the mix is - special bathroom cement stuff to go above the heating cables and on which the tiles will be placed) has been poured. I don't know how long it needs to dry before they can continue - so I'm not sure what'll happen tomorrow.",[211,418,419],{},[217,420],{"alt":219,"src":421},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F02-bathroom-01.jpg",{"title":227,"searchDepth":228,"depth":228,"links":423},[],"2013-12-02 17:59 +0100",{},"\u002F2013\u002F12\u002F02\u002Fbathroom-end-of-day-8",{"title":411,"description":416},{"loc":426},"2013\u002F12\u002F02\u002Fbathroom-end-of-day-8",[240,241],"m6tAkKqI65C4bxqfvlQow1XQuMqjNMx9VT1-LxMz2II",{"id":433,"title":434,"body":435,"category":69,"date":500,"description":439,"embedImage":231,"extension":232,"image":491,"intro":231,"meta":501,"navigation":234,"path":502,"seo":503,"series":219,"sitemap":504,"stem":505,"tags":506,"__hash__":507},"content\u002F2013\u002F12\u002F03\u002Fbathroom-end-of-day-9.md","Bathroom end of day 9",{"type":208,"value":436,"toc":498},[437,440,443,462,468,474,480,486,492],[211,438,439],{},"The floor had obviously dried enough for them to work.",[211,441,442],{},"Progress today:",[444,445,446,450,453,456,459],"ul",{},[447,448,449],"li",{},"Old ceiling removed - prepared for the downlights",[447,451,452],{},"Cable pull tubes installed",[447,454,455],{},"Door between the bathroom and bedroom removed (this will become a wall containing the pipe-in-pipe inspection box)",[447,457,458],{},"Some pipe-in-pipe outer tubes and manifolds installed",[447,460,461],{},"End wall inner panel (chipboard or similar) mounted (around window)",[211,463,464],{},[217,465],{"alt":466,"src":467},"End wall showing window and some pipe-in-pipe manifolds","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F03-bathroom-01.jpg",[211,469,470],{},[217,471],{"alt":472,"src":473},"Ceiling prepared for downlights","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F03-bathroom-02.jpg",[211,475,476],{},[217,477],{"alt":478,"src":479},"Pipe-in-pipe inspection box in the old doorway (will be a wall)","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F03-bathroom-03.jpg",[211,481,482],{},[217,483],{"alt":484,"src":485},"Electic pull tubes in place","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F03-bathroom-04.jpg",[211,487,488],{},[217,489],{"alt":490,"src":491},"Close-up of the pipe-in-pipe manifolds for the bath","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F03-bathroom-05.jpg",[211,493,494],{},[217,495],{"alt":496,"src":497},"Close-up of the end wall showing both bath and basin connections","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F03-bathroom-06.jpg",{"title":227,"searchDepth":228,"depth":228,"links":499},[],"2013-12-03 16:47 +0100",{},"\u002F2013\u002F12\u002F03\u002Fbathroom-end-of-day-9",{"title":434,"description":439},{"loc":502},"2013\u002F12\u002F03\u002Fbathroom-end-of-day-9",[240,241],"3FCWSNfM6S2yeJzm01TOGgyNP8BIvYpTdc3VCJ4JoW4",{"id":509,"title":510,"body":511,"category":69,"date":530,"description":515,"embedImage":231,"extension":232,"image":521,"intro":231,"meta":531,"navigation":234,"path":532,"seo":533,"series":219,"sitemap":534,"stem":535,"tags":536,"__hash__":537},"content\u002F2013\u002F12\u002F04\u002Fbathroom-end-of-day-10.md","Bathroom end of day 10",{"type":208,"value":512,"toc":528},[513,516,522],[211,514,515],{},"Plasterboard ceiling mounted. More wall board put up (no longer a hole thru to the bedroom). This chipboard will get a layer of special panels (Litex) and then on the outside of that the tiles will be put up.",[211,517,518],{},[217,519],{"alt":520,"src":521},"Long wall showing where the doorframe used to be","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F04-bathroom-01.jpg",[211,523,524],{},[217,525],{"alt":526,"src":527},"Doorframe from the bedroom side","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F04-bathroom-02.jpg",{"title":227,"searchDepth":228,"depth":228,"links":529},[],"2013-12-04 20:15 +0100",{},"\u002F2013\u002F12\u002F04\u002Fbathroom-end-of-day-10",{"title":510,"description":515},{"loc":532},"2013\u002F12\u002F04\u002Fbathroom-end-of-day-10",[240,241],"dqPhPomMgZo8QZ6LUWDLt70jx4dBwFzx_TsHoEGMSNQ",{"id":539,"title":540,"body":541,"category":69,"date":568,"description":545,"embedImage":231,"extension":232,"image":550,"intro":231,"meta":569,"navigation":234,"path":570,"seo":571,"series":219,"sitemap":572,"stem":573,"tags":574,"__hash__":575},"content\u002F2013\u002F12\u002F05\u002Fbathroom-end-of-day-11.md","Bathroom end of day 11",{"type":208,"value":542,"toc":566},[543,546,551,556,561],[211,544,545],{},"Litex panels now mounted (but not yet sealed).",[211,547,548],{},[217,549],{"alt":219,"src":550},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F05-bathroom-01.jpg",[211,552,553],{},[217,554],{"alt":219,"src":555},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F05-bathroom-02.jpg",[211,557,558],{},[217,559],{"alt":219,"src":560},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F05-bathroom-03.jpg",[211,562,563],{},[217,564],{"alt":219,"src":565},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F05-bathroom-04.jpg",{"title":227,"searchDepth":228,"depth":228,"links":567},[],"2013-12-05 19:25 +0100",{},"\u002F2013\u002F12\u002F05\u002Fbathroom-end-of-day-11",{"title":540,"description":545},{"loc":570},"2013\u002F12\u002F05\u002Fbathroom-end-of-day-11",[240,241],"hfhO_EaSXibZtJ8kUq9dCb6tVR06vXcbaLdkFvjpBEE",{"id":577,"title":578,"body":579,"category":69,"date":604,"description":583,"embedImage":231,"extension":232,"image":591,"intro":231,"meta":605,"navigation":234,"path":606,"seo":607,"series":219,"sitemap":608,"stem":609,"tags":610,"__hash__":611},"content\u002F2013\u002F12\u002F07\u002Fbathroom-end-of-day-13.md","Bathroom end of day 13",{"type":208,"value":580,"toc":602},[581,584,587,592,597],[211,582,583],{},"Yesterday (Friday) and today they sealed the litex panels with a layer of a liquid membrane.",[211,585,586],{},"On Monday they'll give it a second layer - and then on Tuesday they'll start laying the tiles.",[211,588,589],{},[217,590],{"alt":219,"src":591},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F07-bathroom-01.jpg",[211,593,594],{},[217,595],{"alt":219,"src":596},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F07-bathroom-02.jpg",[211,598,599],{},[217,600],{"alt":219,"src":601},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F07-bathroom-03.jpg",{"title":227,"searchDepth":228,"depth":228,"links":603},[],"2013-12-07 17:18 +0100",{},"\u002F2013\u002F12\u002F07\u002Fbathroom-end-of-day-13",{"title":578,"description":583},{"loc":606},"2013\u002F12\u002F07\u002Fbathroom-end-of-day-13",[240,241],"Vbrho602iy8u99C7MY-P03TsFlbKmatgxsW0lnWDwMQ",{"id":613,"title":614,"body":615,"category":69,"date":637,"description":619,"embedImage":231,"extension":232,"image":624,"intro":231,"meta":638,"navigation":234,"path":639,"seo":640,"series":219,"sitemap":641,"stem":642,"tags":643,"__hash__":644},"content\u002F2013\u002F12\u002F09\u002Fbathroom-end-of-day-15.md","Bathroom end of day 15",{"type":208,"value":616,"toc":635},[617,620,625,630],[211,618,619],{},"Second layer of the liquid membrane. If I understand them right that means that tile laying can start tomorrow.",[211,621,622],{},[217,623],{"alt":219,"src":624},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F09-bathroom-01.jpg",[211,626,627],{},[217,628],{"alt":219,"src":629},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F09-bathroom-02.jpg",[211,631,632],{},[217,633],{"alt":219,"src":634},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F09-bathroom-03.jpg",{"title":227,"searchDepth":228,"depth":228,"links":636},[],"2013-12-09 22:29 +0100",{},"\u002F2013\u002F12\u002F09\u002Fbathroom-end-of-day-15",{"title":614,"description":619},{"loc":639},"2013\u002F12\u002F09\u002Fbathroom-end-of-day-15",[240,241],"AHlxw-lvUJ-Qfpbz-RJyT1Yy5xFS0G74Ljh_rrdvcQA",{"id":646,"title":647,"body":648,"category":69,"date":682,"description":652,"embedImage":231,"extension":232,"image":661,"intro":231,"meta":683,"navigation":234,"path":684,"seo":685,"series":219,"sitemap":686,"stem":687,"tags":688,"__hash__":689},"content\u002F2013\u002F12\u002F12\u002Fbathroom-end-of-day-18.md","Bathroom end of day 18",{"type":208,"value":649,"toc":680},[650,653,656,662,668,674],[211,651,652],{},"Tiling has been happening the last two days.",[211,654,655],{},"A lot of the wall tiles are done - including the gap where a mirror will be placed. The small mosaic tiles hanging in the edges of that gap will also be used as sides to the bath. They are the same as the floor tiles apart from size.",[211,657,658],{},[217,659],{"alt":660,"src":661},"Window end of the room - mirror slot visible","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F12-bathroom-01.jpg",[211,663,664],{},[217,665],{"alt":666,"src":667},"Long view of the room","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F12-bathroom-02.jpg",[211,669,670],{},[217,671],{"alt":672,"src":673},"Wall above where the bath will stand still to be done","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F12-bathroom-03.jpg",[211,675,676],{},[217,677],{"alt":678,"src":679},"Mirror slot face on","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F12-bathroom-04.jpg",{"title":227,"searchDepth":228,"depth":228,"links":681},[],"2013-12-12 18:21 +0100",{},"\u002F2013\u002F12\u002F12\u002Fbathroom-end-of-day-18",{"title":647,"description":652},{"loc":684},"2013\u002F12\u002F12\u002Fbathroom-end-of-day-18",[240,241],"o7K181Y6N6DfVIfq1WyjJnChPnINk9ee0hk33Nuz_Pk",{"id":691,"title":692,"body":693,"category":69,"date":716,"description":697,"embedImage":231,"extension":232,"image":708,"intro":231,"meta":717,"navigation":234,"path":718,"seo":719,"series":219,"sitemap":720,"stem":721,"tags":722,"__hash__":723},"content\u002F2013\u002F12\u002F13\u002Fbathroom-end-of-day-19.md","Bathroom end of day 19",{"type":208,"value":694,"toc":714},[695,698,701,704,709],[211,696,697],{},"More tiling. Floor tiles started.",[211,699,700],{},"The tile layer will be coming tomorrow (Sat) to finish the laying so that everything can be ready in time for the plumber on Tuesday and the electrician on Wednesday.",[211,702,703],{},"Finish is estimated Thursday\u002FFriday apart from the window.",[211,705,706],{},[217,707],{"alt":219,"src":708},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F13-bathroom-01.jpg",[211,710,711],{},[217,712],{"alt":219,"src":713},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F13-bathroom-02.jpg",{"title":227,"searchDepth":228,"depth":228,"links":715},[],"2013-12-13 17:44 +0100",{},"\u002F2013\u002F12\u002F13\u002Fbathroom-end-of-day-19",{"title":692,"description":697},{"loc":718},"2013\u002F12\u002F13\u002Fbathroom-end-of-day-19",[240,241],"aIYVi2B5OS5xFaZnmfflKAYmy9ml7sjppVP1Z47pxW0",{"id":725,"title":726,"body":727,"category":69,"date":744,"description":731,"embedImage":231,"extension":232,"image":736,"intro":231,"meta":745,"navigation":234,"path":746,"seo":747,"series":219,"sitemap":748,"stem":749,"tags":750,"__hash__":751},"content\u002F2013\u002F12\u002F14\u002Fbathroom-end-of-day-20.md","Bathroom end of day 20",{"type":208,"value":728,"toc":742},[729,732,737],[211,730,731],{},"Tiling done apart from grouting.",[211,733,734],{},[217,735],{"alt":219,"src":736},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F14-bathroom-01.jpg",[211,738,739],{},[217,740],{"alt":219,"src":741},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F14-bathroom-02.jpg",{"title":227,"searchDepth":228,"depth":228,"links":743},[],"2013-12-14 18:01 +0100",{},"\u002F2013\u002F12\u002F14\u002Fbathroom-end-of-day-20",{"title":726,"description":731},{"loc":746},"2013\u002F12\u002F14\u002Fbathroom-end-of-day-20",[240,241],"ww-nuKm_c5iGlJfWzoq8cMkhi31uBr8dvlXibVclU6M",{"id":753,"title":754,"body":755,"category":69,"date":801,"description":759,"embedImage":231,"extension":232,"image":777,"intro":231,"meta":802,"navigation":234,"path":803,"seo":804,"series":219,"sitemap":805,"stem":806,"tags":807,"__hash__":808},"content\u002F2013\u002F12\u002F16\u002Fbathroom-end-of-day-22.md","Bathroom end of day 22",{"type":208,"value":756,"toc":799},[757,760,763,766,769,772,778,783,789,794],[211,758,759],{},"Grouting and silicone done.",[211,761,762],{},"First two pics are pre-silicon with the grouting drying so look washed in grey. The last three are afterwards with most of the grout dust removed and the silicone in place.",[211,764,765],{},"Tomorrow (Tuesday) is the bath and the sink (not entirely sure about the sink - might need to wait with that until the bath is in place and tiled in).",[211,767,768],{},"Wednesday is the electrician. Probably the backside of the door that used to go thru to the bedroom too.",[211,770,771],{},"Left to do will be the window (including the tiles around it) which is waiting on a new window due early new year. Takes at least 6 weeks to make a window apparently.",[211,773,774],{},[217,775],{"alt":776,"src":777},"Grouting done and drying","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F16-bathroom-01.jpg",[211,779,780],{},[217,781],{"alt":776,"src":782},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F16-bathroom-02.jpg",[211,784,785],{},[217,786],{"alt":787,"src":788},"Grouting dried and cleaned, silicone in place","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F16-bathroom-03.jpg",[211,790,791],{},[217,792],{"alt":787,"src":793},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F16-bathroom-04.jpg",[211,795,796],{},[217,797],{"alt":787,"src":798},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F16-bathroom-05.jpg",{"title":227,"searchDepth":228,"depth":228,"links":800},[],"2013-12-16 19:11 +0100",{},"\u002F2013\u002F12\u002F16\u002Fbathroom-end-of-day-22",{"title":754,"description":759},{"loc":803},"2013\u002F12\u002F16\u002Fbathroom-end-of-day-22",[240,241],"1XgmciWcbl4au9P7Jvz7I1ZOZQK44_Emx4ZPM7Kfz6g",{"id":810,"title":811,"body":812,"category":69,"date":854,"description":816,"embedImage":231,"extension":232,"image":834,"intro":231,"meta":855,"navigation":234,"path":856,"seo":857,"series":219,"sitemap":858,"stem":859,"tags":860,"__hash__":861},"content\u002F2013\u002F12\u002F17\u002Fbathroom-end-of-day-23.md","Bathroom end of day 23",{"type":208,"value":813,"toc":852},[814,817,820,823,826,829,835,840,846],[211,815,816],{},"Bath in place and boxed in. Tiling still to do. The room look a lot smaller now - almost back to the size it was when we started :) But the bath is 20cm longer and wider than the old one. Something that the kids are looking forward to using.",[211,818,819],{},"New radiator mounted but not plumbed in yet.",[211,821,822],{},"Work started on the bedroom side of the blocked off door.",[211,824,825],{},"The window we're waiting for may delay the fitting of the sink it seems - since it can't be fully mounted until the tiling above is done. Shame that a window takes 6 weeks when you're otherwise on a four week project.",[211,827,828],{},"We'll see what happens tomorrow.",[211,830,831],{},[217,832],{"alt":833,"src":834},"Bath and radiator","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F17-bathroom-01.jpg",[211,836,837],{},[217,838],{"alt":833,"src":839},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F17-bathroom-02.jpg",[211,841,842],{},[217,843],{"alt":844,"src":845},"Bath","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F17-bathroom-03.jpg",[211,847,848],{},[217,849],{"alt":850,"src":851},"Bedroom side of the blocked off door","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F17-bathroom-04.jpg",{"title":227,"searchDepth":228,"depth":228,"links":853},[],"2013-12-17 20:51 +0100",{},"\u002F2013\u002F12\u002F17\u002Fbathroom-end-of-day-23",{"title":811,"description":816},{"loc":856},"2013\u002F12\u002F17\u002Fbathroom-end-of-day-23",[240,241],"gWQI7pYVebZIPxEjY0NztYQnLFsjtsHGhJ-Huh_h-gE",{"id":863,"title":864,"body":865,"category":69,"date":902,"description":869,"embedImage":231,"extension":232,"image":887,"intro":231,"meta":903,"navigation":234,"path":904,"seo":905,"series":219,"sitemap":906,"stem":907,"tags":908,"__hash__":909},"content\u002F2013\u002F12\u002F18\u002Fbathroom-end-of-day-24.md","Bathroom end of day 24",{"type":208,"value":866,"toc":900},[867,870,873,876,879,882,888,894],[211,868,869],{},"Bath tiled (grouting still to come).",[211,871,872],{},"Sink mounted (and plumbed).",[211,874,875],{},"Radiator plumbed in.",[211,877,878],{},"Electrics done. Lights, warmth etc all working.",[211,880,881],{},"The only issue today was that when the plumber looked at the shower he'd fetched - it was one for a shower cubicle (in other words no way to fill the bath). He's going to go get the right one tomorrow (cross fingers that it's in stock).",[211,883,884],{},[217,885],{"alt":886,"src":887},"Bath and sink","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F18-bathroom-01.jpg",[211,889,890],{},[217,891],{"alt":892,"src":893},"Bath, sink and radiator","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F18-bathroom-02.jpg",[211,895,896],{},[217,897],{"alt":898,"src":899},"Bath showing tiling","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F18-bathroom-03.jpg",{"title":227,"searchDepth":228,"depth":228,"links":901},[],"2013-12-18 18:25 +0100",{},"\u002F2013\u002F12\u002F18\u002Fbathroom-end-of-day-24",{"title":864,"description":869},{"loc":904},"2013\u002F12\u002F18\u002Fbathroom-end-of-day-24",[240,241],"AxzcTDJTl2pZCi65AQ5ACPh4c23NT5UftqAQRe8kpJo",{"id":911,"title":912,"body":913,"category":69,"date":967,"description":917,"embedImage":231,"extension":232,"image":946,"intro":231,"meta":968,"navigation":234,"path":969,"seo":970,"series":219,"sitemap":971,"stem":972,"tags":973,"__hash__":974},"content\u002F2013\u002F12\u002F19\u002Fbathroom-end-of-day-25.md","Bathroom end of day 25",{"type":208,"value":914,"toc":965},[915,918,921,924,927,938,941,947,953,959],[211,916,917],{},"Bath grouted (silicone tomorrow).",[211,919,920],{},"Shower mounted.",[211,922,923],{},"Doorframe refitted, first coat of paint on frame and door.",[211,925,926],{},"Tomorrow:",[444,928,929,932,935],{},[447,930,931],{},"Door - second coat and silicon between frame and tiles",[447,933,934],{},"Bath - silicone round edges",[447,936,937],{},"Mirror",[211,939,940],{},"Waiting on the new window and the towel cupboard - both have a delivery mid January. Glass shower panel will be fitted about the same time between the bath and the sink (has to wait on the window).",[211,942,943],{},[217,944],{"alt":945,"src":946},"Shower","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F19-bathroom-01.jpg",[211,948,949],{},[217,950],{"alt":951,"src":952},"Bath tiles grouted","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F19-bathroom-02.jpg",[211,954,955],{},[217,956],{"alt":957,"src":958},"Door","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F19-bathroom-03.jpg",[211,960,961],{},[217,962],{"alt":963,"src":964},"Shower head","\u002Fimages\u002Fposts\u002F2013\u002F12\u002F19-bathroom-04.jpg",{"title":227,"searchDepth":228,"depth":228,"links":966},[],"2013-12-19 20:46 +0100",{},"\u002F2013\u002F12\u002F19\u002Fbathroom-end-of-day-25",{"title":912,"description":917},{"loc":969},"2013\u002F12\u002F19\u002Fbathroom-end-of-day-25",[240,241],"k4OKS3laNKwVW-IGuMwHFb9py-ikt-QCFHC23qwOAuM",{"id":976,"title":977,"body":978,"category":69,"date":1009,"description":982,"embedImage":231,"extension":232,"image":996,"intro":231,"meta":1010,"navigation":234,"path":1011,"seo":1012,"series":219,"sitemap":1013,"stem":1014,"tags":1015,"__hash__":1016},"content\u002F2013\u002F12\u002F21\u002Fbathroom-end-of-day-26.md","Bathroom end of day 26",{"type":208,"value":979,"toc":1007},[980,983,986,989,992,997,1002],[211,981,982],{},"Bath silicone done.",[211,984,985],{},"Doorframe painted and silicone along edges where it meets tiles.",[211,987,988],{},"Mirror in place - glue still drying so can't fill the crack around the edge just yet.",[211,990,991],{},"All finishing touches will be taken when the new window and the towel cupboard arrive - both have a delivery mid January. Glass shower panel will be fitted about the same time between the bath and the sink (has to wait on the window).",[211,993,994],{},[217,995],{"alt":937,"src":996},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F21-bathroom-01.jpg",[211,998,999],{},[217,1000],{"alt":844,"src":1001},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F21-bathroom-02.jpg",[211,1003,1004],{},[217,1005],{"alt":957,"src":1006},"\u002Fimages\u002Fposts\u002F2013\u002F12\u002F21-bathroom-03.jpg",{"title":227,"searchDepth":228,"depth":228,"links":1008},[],"2013-12-21 12:41 +0100",{},"\u002F2013\u002F12\u002F21\u002Fbathroom-end-of-day-26",{"title":977,"description":982},{"loc":1011},"2013\u002F12\u002F21\u002Fbathroom-end-of-day-26",[240,241],"y0ISCPiz09ogwLJv1mzcmlrN5AsXhs8zw7ru3BI5Hwo",{"id":1018,"title":1019,"body":1020,"category":69,"date":1042,"description":1024,"embedImage":231,"extension":232,"image":1033,"intro":1043,"meta":1044,"navigation":234,"path":1045,"seo":1046,"series":219,"sitemap":1047,"stem":1048,"tags":1049,"__hash__":1050},"content\u002F2014\u002F01\u002F13\u002Fbathroom-window-started.md","Bathroom window started",{"type":208,"value":1021,"toc":1040},[1022,1025,1028,1034],[211,1023,1024],{},"The bathroom window has now arrived (was always going to be at least 2 weeks longer than the original work given that it had a 6 week lead time).",[211,1026,1027],{},"This means that we're getting the last touches done. Starting with the tiling round the window.",[211,1029,1030],{},[217,1031],{"alt":1032,"src":1033},"Window","\u002Fimages\u002Fposts\u002F2014\u002F01\u002Fbathroom-01.jpg",[211,1035,1036],{},[217,1037],{"alt":1038,"src":1039},"Window and tiling","\u002Fimages\u002Fposts\u002F2014\u002F01\u002Fbathroom-02.jpg",{"title":227,"searchDepth":228,"depth":228,"links":1041},[],"2014-01-13 16:19 +0100","The bathroom window has now arrived - getting the last touches done",{},"\u002F2014\u002F01\u002F13\u002Fbathroom-window-started",{"title":1019,"description":1024},{"loc":1045},"2014\u002F01\u002F13\u002Fbathroom-window-started",[240,241],"YaQqdb0seNs3zO-Y8iLBh0Ai2FwMbf5d-673N9TyO00",{"id":1052,"title":1053,"body":1054,"category":138,"date":1097,"description":1058,"embedImage":231,"extension":232,"image":1094,"intro":1098,"meta":1099,"navigation":234,"path":1100,"seo":1101,"series":1102,"sitemap":1103,"stem":1104,"tags":1105,"__hash__":1108},"content\u002F2017\u002F08\u002F31\u002Fbase-and-bumps.md","Base and bumps",{"type":208,"value":1055,"toc":1095},[1056,1059,1062,1065,1071,1074,1077,1080,1086,1089],[211,1057,1058],{},"The Dalek build has been started.",[211,1060,1061],{},"The base front and back parts have been printed.",[211,1063,1064],{},"They show the footprint of the finished model when placed together:",[211,1066,1067],{},[217,1068],{"alt":1069,"src":1070},"The two base parts placed together","\u002Fimages\u002Fposts\u002F2017\u002F08\u002Fbase.jpg",[211,1072,1073],{},"Oddly - the quality on the front (printed first) is quite a bit higher than on the rear despite them being the same height, same cross section for the most part, same slicing configuration, same temperatures, same filament. Not sure what happened there.",[211,1075,1076],{},"But - since it's going to be painted and therefore will need to be filled and sanded - this isn't a huge issue.",[211,1078,1079],{},"The base is 26cm front to back and 19,5cm across.",[211,1081,1082],{},[217,1083],{"alt":1084,"src":1085},"Close-up of the base parts showing the rear (left) and front (right)","\u002Fimages\u002Fposts\u002F2017\u002F08\u002Fbase-print-quality.jpg",[211,1087,1088],{},"Finally - the first of two sets of bumps. These hemispheres go on the outside of the dalek's skirt. Needed 56 in all - and I could fit 30 on a plate easily - so I'll just use the best 56 of 60.",[211,1090,1091],{},[217,1092],{"alt":1093,"src":1094},"Hemispheres on the build plate","\u002Fimages\u002Fposts\u002F2017\u002F08\u002Fbumps.jpg",{"title":227,"searchDepth":228,"depth":228,"links":1096},[],"2017-08-31 17:54 +0200","Getting started on the Dalek build",{},"\u002F2017\u002F08\u002F31\u002Fbase-and-bumps",{"title":1053,"description":1058},"Building a Dalek - 3D Print",{"loc":1100},"2017\u002F08\u002F31\u002Fbase-and-bumps",[1106,1107],"3d printing","dalek","BN-yr7SyqljN2tPRBvrvhlBBAaJWr6kQEyOYQ2YYONU",{"id":1110,"title":1111,"body":1112,"category":138,"date":1165,"description":1166,"embedImage":1139,"extension":232,"image":231,"intro":1167,"meta":1168,"navigation":234,"path":1169,"seo":1170,"series":1102,"sitemap":1171,"stem":1172,"tags":1173,"__hash__":1174},"content\u002F2017\u002F08\u002F31\u002Fi-feel-i-have-to-build-a-dalek.md","I feel I have to build a Dalek",{"type":208,"value":1113,"toc":1163},[1114,1134,1140,1143,1146,1149,1157,1160],[211,1115,1116,1117,1127,1128,1133],{},"While I was digging for some inspiration for a new 3d model I happened across the ",[1118,1119,1126],"a",{"href":1120,"rel":1121,"target":1125},"https:\u002F\u002Fwww.thingiverse.com\u002Fthing:369866",[1122,1123,1124],"nofollow","noopener","noreferer","_blank","Accurate Dalek Model"," by user ",[1118,1129,1132],{"href":1130,"rel":1131,"target":1125},"https:\u002F\u002Fwww.thingiverse.com\u002FAudrey2\u002Fabout",[1122,1123,1124],"Audrey2",".",[211,1135,1136],{},[217,1137],{"alt":1138,"src":1139},"Accurate Dalek","https:\u002F\u002Fcdn.thingiverse.com\u002Frenders\u002F92\u002Fa1\u002F4c\u002F19\u002Fb5\u002F004_display_large.jpg",[211,1141,1142],{},"This brings back memories of being terrified as a kid by these fantastic things - and I feel the urge to build one.",[211,1144,1145],{},"I've worked out that I can scale to 200% - the largest footprint are the two base parts - and 200% just fits on the build plate.",[211,1147,1148],{},"So - to do this it's going to take a fair bit of printing, a lot of priming and sanding and then painting.",[444,1150,1151,1154],{},[447,1152,1153],{},"Can I add some LEDs to get the eye light and the ears to flash?",[447,1155,1156],{},"Can I add sound?",[211,1158,1159],{},"Let's see where this goes.",[211,1161,1162],{},"Stay tuned ...",{"title":227,"searchDepth":228,"depth":228,"links":1164},[],"2017-08-31 12:19 +0200","While I was digging for some inspiration for a new 3d model I happened across the Accurate Dalek Model by user Audrey2.","So - the sudden urge for more daleks in your life might be unavoidable?",{},"\u002F2017\u002F08\u002F31\u002Fi-feel-i-have-to-build-a-dalek",{"title":1111,"description":1166},{"loc":1169},"2017\u002F08\u002F31\u002Fi-feel-i-have-to-build-a-dalek",[1106,1107],"LUyC4Xsij7zWC_6_o5xrxfiCLMMpIq6wXqVqMQAJUXE",{"id":1176,"title":1177,"body":1178,"category":138,"date":1199,"description":1200,"embedImage":231,"extension":232,"image":231,"intro":1201,"meta":1202,"navigation":234,"path":1203,"seo":1204,"series":1102,"sitemap":1205,"stem":1206,"tags":1207,"__hash__":1208},"content\u002F2017\u002F09\u002F01\u002Fproject-dalek.md","Project Dalek",{"type":208,"value":1179,"toc":1197},[1180,1189],[211,1181,1182,1183,1188],{},"Did some digging - and have found several voice modulators for arduino out there that can make you sound like you're a dalek. Some of them also do the lights control. Some mention the ",[1118,1184,1187],{"href":1185,"rel":1186,"target":1125},"https:\u002F\u002Fwww.adafruit.com\u002Fproduct\u002F94",[1122,1123,1124],"wave arduino shield"," from adafruit as a useful addition.",[211,1190,1191,1192,1196],{},"Didn't find exactly what I want (I'm more interested in one that can take playback rather than a microphone) but I did find that lots of things link to ",[1118,1193,1177],{"href":1194,"rel":1195,"target":1125},"http:\u002F\u002Fwww.projectdalek.com\u002F",[1122,1123,1124]," - looks like a really useful resource.",{"title":227,"searchDepth":228,"depth":228,"links":1198},[],"2017-09-01 06:46 +0200","Did some digging - and have found several voice modulators for arduino out there that can make you sound like you're a dalek. Some of them also do the lights control. Some mention the wave arduino shield from adafruit as a useful addition.","So - you want to build a dalek - where to get information?",{},"\u002F2017\u002F09\u002F01\u002Fproject-dalek",{"title":1177,"description":1200},{"loc":1203},"2017\u002F09\u002F01\u002Fproject-dalek",[1107],"glwOm2JrtxoF3wb--BxcCdz3s0w6ut8akOlxTFxH0oo",{"id":1210,"title":1211,"body":1212,"category":231,"date":1223,"description":1224,"embedImage":231,"extension":232,"image":231,"intro":1225,"meta":1226,"navigation":234,"path":1227,"seo":1228,"series":1102,"sitemap":1229,"stem":1230,"tags":1231,"__hash__":1232},"content\u002F2017\u002F09\u002F01\u002Ftime-war-dalek-2005.md","Time War Dalek - 2005",{"type":208,"value":1213,"toc":1221},[1214],[211,1215,1216,1217],{},"From what I can see - it looks like the model I'll be building is a ",[1118,1218,1211],{"href":1219,"rel":1220,"target":1125},"http:\u002F\u002Fwww.thedoctorwhosite.co.uk\u002Fdalek\u002Ftypes\u002F19-time-war-daleks\u002F",[1122,1123,1124],{"title":227,"searchDepth":228,"depth":228,"links":1222},[],"2017-09-01 17:54 +0200","From what I can see - it looks like the model I'll be building is a Time War Dalek - 2005","Time to build a Time War Dalek",{},"\u002F2017\u002F09\u002F01\u002Ftime-war-dalek-2005",{"title":1211,"description":1224},{"loc":1227},"2017\u002F09\u002F01\u002Ftime-war-dalek-2005",[1107],"wh_4d5IaUKM58Y4MWBrt1QxI_Y618OdSTl8ZILz4kVU",{"id":1234,"title":1235,"body":1236,"category":27,"date":1333,"description":1334,"embedImage":231,"extension":232,"image":231,"intro":1335,"meta":1336,"navigation":234,"path":1338,"seo":1339,"series":1340,"sitemap":1341,"stem":1342,"tags":1343,"__hash__":1347},"content\u002F2019\u002F04\u002F25\u002Fkafka-java-to-scala-introduction.md","Kafka - java to scala - introduction",{"type":208,"value":1237,"toc":1328},[1238,1247,1253,1258,1294,1298,1305,1309,1316,1325],[211,1239,1240,1241,1246],{},"I was recently a participant on a ",[1118,1242,1245],{"href":1243,"rel":1244,"target":1125},"https:\u002F\u002Fwww.confluent.io\u002Ftraining\u002F",[1122,1123,1124],"Confluent on-premesis kafka course",". While working through the labs (which are in java), for fun I tried the same code in kotlin. That was fun - but I thought it could be a nice exercise to convert from java to scala - step by step - and maybe learn some new scala stuff on the way. It will assume some level of kafka knowledge - what is a producer, consumer, topic etc.",[211,1248,1249],{},[1250,1251,1252],"em",{},"It is important to understand that it is written from my viewpoint - someone who has played with scala, likes it, but has never really had time to get into it - so this will be somewhat of a discovery journey for me too.",[1254,1255,1257],"h2",{"id":1256},"posts-in-this-series","Posts in this series",[1259,1260,1261,1268,1275,1282,1288],"ol",{},[447,1262,1263,1267],{},[1118,1264,1266],{"href":1265},"\u002F2019\u002F04\u002F25\u002Fkafka-java-to-scala-java\u002F","A simple java example running"," - a basic producer and consumer pair",[447,1269,1270,1274],{},[1118,1271,1273],{"href":1272},"\u002F2019\u002F04\u002F30\u002Fkafka-java-to-scala-scala-v1\u002F","Convert java to scala"," - conversion of the basic producer and consumer to scala - on an almost line by line basis",[447,1276,1277,1281],{},[1118,1278,1280],{"href":1279},"\u002F2019\u002F05\u002F03\u002Fkafka-java-to-scala-scala-v2\u002F","Update the scala version"," to be more \"scala-like\" and add a config file instead of hard coded values",[447,1283,1284],{},[1118,1285,1287],{"href":1286},"\u002F2019\u002F05\u002F08\u002Fkafka-java-to-scala-akka-streams-basics\u002F","A slight digression to look at basic akka-streams",[447,1289,1290],{},[1118,1291,1293],{"href":1292},"\u002F2019\u002F05\u002F15\u002Fkafka-java-to-scala-akka-streams-kafka\u002F","Use akka-streams for the scala example",[1254,1295,1297],{"id":1296},"code","Code",[211,1299,1300,1301],{},"All code is available in this github repository: ",[1118,1302,1303],{"href":1303,"rel":1304,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala",[1122,1123,1124],[1254,1306,1308],{"id":1307},"docker","Docker",[211,1310,1311,1312,1315],{},"All of the related articles assume that you have kafka running using the supplied ",[1296,1313,1314],{},"docker-compose.yml"," file - so in the working directory - run:",[1317,1318,1323],"pre",{"className":1319,"code":1321,"language":1322},[1320],"language-text","docker-compose up\n","text",[1296,1324,1321],{"__ignoreMap":227},[211,1326,1327],{},"Make sure you have a good amount of memory available to docker - 4-5 Gb minimum :)",{"title":227,"searchDepth":228,"depth":228,"links":1329},[1330,1331,1332],{"id":1256,"depth":228,"text":1257},{"id":1296,"depth":228,"text":1297},{"id":1307,"depth":228,"text":1308},"2019-04-25 12:02 +0200","I was recently a participant on a Confluent on-premesis kafka course. While working through the labs (which are in java), for fun I tried the same code in kotlin. That was fun - but I thought it could be a nice exercise to convert from java to scala - step by step - and maybe learn some new scala stuff on the way. It will assume some level of kafka knowledge - what is a producer, consumer, topic etc.","This series will work through converting java from the confluent on-premesis course to scala",{"updated":1337},"2020-01-23 00:00","\u002F2019\u002F04\u002F25\u002Fkafka-java-to-scala-introduction",{"title":1235,"description":1334},"Kafka - java to scala",{"loc":1338},"2019\u002F04\u002F25\u002Fkafka-java-to-scala-introduction",[1344,1345,1346],"kafka","java","scala","Naj___zaoAJvia_rWonj-KQZKIW2cHUfd0zIbiy89no",{"id":1349,"title":1350,"body":1351,"category":27,"date":2079,"description":1355,"embedImage":231,"extension":232,"image":231,"intro":2080,"meta":2081,"navigation":234,"path":2082,"seo":2083,"series":1340,"sitemap":2084,"stem":2085,"tags":2086,"__hash__":2088},"content\u002F2019\u002F04\u002F25\u002Fkafka-java-to-scala-java.md","Kafka - java to scala - java",{"type":208,"value":1352,"toc":2069},[1353,1356,1363,1367,1375,1379,1387,1390,1505,1508,1535,1538,1691,1697,1702,1705,1714,1717,1723,1726,1730,1738,1741,1849,1852,1878,1881,2028,2034,2038,2046,2049,2055,2058,2062,2065],[211,1354,1355],{},"This series goes through conversion of some basic java kafka clients to scala - step by step. It is important to understand that it is written from my viewpoint - someone who has played with scala, likes it, but has never really had time to get into it.",[211,1357,1358,1359],{},"You will need to have the correct initial setup - see ",[1118,1360,1362],{"href":1361},"\u002F2019\u002F04\u002F25\u002Fkafka-java-to-scala-introduction\u002F","the introduction",[1254,1364,1366],{"id":1365},"basic-java-clients","Basic Java clients",[211,1368,1369,1370,1374],{},"On the ",[1118,1371,1373],{"href":1243,"rel":1372,"target":1125},[1122,1123,1124],"kafka course"," one of the first things you develop is a basic consumer and producer in java.",[1254,1376,1378],{"id":1377},"producer","Producer",[211,1380,1381,1382],{},"Let's take a look at the basic producer. There is one main java file here - ",[1118,1383,1386],{"href":1384,"rel":1385,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Fblob\u002Fmaster\u002Fjava-starter\u002Fproducer\u002Fsrc\u002Fmain\u002Fjava\u002Fnet\u002Fchrissearle\u002Fkafka\u002FBasicProducer.java",[1122,1123,1124],"BasicProducer.java",[211,1388,1389],{},"The main method does three main things:",[1317,1391,1394],{"className":1392,"code":1393,"language":1345,"meta":227,"style":227},"language-java shiki shiki-themes github-dark","Properties settings = new Properties();\n\nsettings.put(\"client.id\", \"basic-producer\");\nsettings.put(\"bootstrap.servers\", \"localhost:29092\");\nsettings.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\nsettings.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n",[1296,1395,1396,1419,1424,1449,1468,1487],{"__ignoreMap":227},[1397,1398,1401,1405,1409,1412,1416],"span",{"class":1399,"line":1400},"line",1,[1397,1402,1404],{"class":1403},"s95oV","Properties settings ",[1397,1406,1408],{"class":1407},"snl16","=",[1397,1410,1411],{"class":1407}," new",[1397,1413,1415],{"class":1414},"svObZ"," Properties",[1397,1417,1418],{"class":1403},"();\n",[1397,1420,1421],{"class":1399,"line":228},[1397,1422,1423],{"emptyLinePlaceholder":234},"\n",[1397,1425,1427,1430,1433,1436,1440,1443,1446],{"class":1399,"line":1426},3,[1397,1428,1429],{"class":1403},"settings.",[1397,1431,1432],{"class":1414},"put",[1397,1434,1435],{"class":1403},"(",[1397,1437,1439],{"class":1438},"sU2Wk","\"client.id\"",[1397,1441,1442],{"class":1403},", ",[1397,1444,1445],{"class":1438},"\"basic-producer\"",[1397,1447,1448],{"class":1403},");\n",[1397,1450,1452,1454,1456,1458,1461,1463,1466],{"class":1399,"line":1451},4,[1397,1453,1429],{"class":1403},[1397,1455,1432],{"class":1414},[1397,1457,1435],{"class":1403},[1397,1459,1460],{"class":1438},"\"bootstrap.servers\"",[1397,1462,1442],{"class":1403},[1397,1464,1465],{"class":1438},"\"localhost:29092\"",[1397,1467,1448],{"class":1403},[1397,1469,1471,1473,1475,1477,1480,1482,1485],{"class":1399,"line":1470},5,[1397,1472,1429],{"class":1403},[1397,1474,1432],{"class":1414},[1397,1476,1435],{"class":1403},[1397,1478,1479],{"class":1438},"\"key.serializer\"",[1397,1481,1442],{"class":1403},[1397,1483,1484],{"class":1438},"\"org.apache.kafka.common.serialization.StringSerializer\"",[1397,1486,1448],{"class":1403},[1397,1488,1490,1492,1494,1496,1499,1501,1503],{"class":1399,"line":1489},6,[1397,1491,1429],{"class":1403},[1397,1493,1432],{"class":1414},[1397,1495,1435],{"class":1403},[1397,1497,1498],{"class":1438},"\"value.serializer\"",[1397,1500,1442],{"class":1403},[1397,1502,1484],{"class":1438},[1397,1504,1448],{"class":1403},[211,1506,1507],{},"This sets up the configuration of the kafka producer that we want and says we are working with records that use String both as key and value.",[1317,1509,1511],{"className":1392,"code":1510,"language":1345,"meta":227,"style":227},"KafkaProducer\u003CString, String> producer = new KafkaProducer\u003C>(settings);\n",[1296,1512,1513],{"__ignoreMap":227},[1397,1514,1515,1518,1521,1523,1525,1528,1530,1532],{"class":1399,"line":1400},[1397,1516,1517],{"class":1403},"KafkaProducer\u003C",[1397,1519,1520],{"class":1407},"String",[1397,1522,1442],{"class":1403},[1397,1524,1520],{"class":1407},[1397,1526,1527],{"class":1403},"> producer ",[1397,1529,1408],{"class":1407},[1397,1531,1411],{"class":1407},[1397,1533,1534],{"class":1403}," KafkaProducer\u003C>(settings);\n",[211,1536,1537],{},"This creates the producer with the supplied configuration.",[1317,1539,1541],{"className":1392,"code":1540,"language":1345,"meta":227,"style":227},"for (int i = 1; i \u003C= 5; i++) {\n    final String key = \"key-\" + i;\n    final String value = \"value-\" + i;\n\n    System.out.println(\"### Sending \" + i + \" ###\");\n\n    final ProducerRecord\u003CString, String> record = new ProducerRecord\u003C>(TOPIC, key, value);\n    producer.send(record);\n}\n",[1296,1542,1543,1581,1600,1616,1620,1645,1649,1673,1685],{"__ignoreMap":227},[1397,1544,1545,1548,1551,1554,1557,1559,1563,1566,1569,1572,1575,1578],{"class":1399,"line":1400},[1397,1546,1547],{"class":1407},"for",[1397,1549,1550],{"class":1403}," (",[1397,1552,1553],{"class":1407},"int",[1397,1555,1556],{"class":1403}," i ",[1397,1558,1408],{"class":1407},[1397,1560,1562],{"class":1561},"sDLfK"," 1",[1397,1564,1565],{"class":1403},"; i ",[1397,1567,1568],{"class":1407},"\u003C=",[1397,1570,1571],{"class":1561}," 5",[1397,1573,1574],{"class":1403},"; i",[1397,1576,1577],{"class":1407},"++",[1397,1579,1580],{"class":1403},") {\n",[1397,1582,1583,1586,1589,1591,1594,1597],{"class":1399,"line":228},[1397,1584,1585],{"class":1407},"    final",[1397,1587,1588],{"class":1403}," String key ",[1397,1590,1408],{"class":1407},[1397,1592,1593],{"class":1438}," \"key-\"",[1397,1595,1596],{"class":1407}," +",[1397,1598,1599],{"class":1403}," i;\n",[1397,1601,1602,1604,1607,1609,1612,1614],{"class":1399,"line":1426},[1397,1603,1585],{"class":1407},[1397,1605,1606],{"class":1403}," String value ",[1397,1608,1408],{"class":1407},[1397,1610,1611],{"class":1438}," \"value-\"",[1397,1613,1596],{"class":1407},[1397,1615,1599],{"class":1403},[1397,1617,1618],{"class":1399,"line":1451},[1397,1619,1423],{"emptyLinePlaceholder":234},[1397,1621,1622,1625,1628,1630,1633,1635,1637,1640,1643],{"class":1399,"line":1470},[1397,1623,1624],{"class":1403},"    System.out.",[1397,1626,1627],{"class":1414},"println",[1397,1629,1435],{"class":1403},[1397,1631,1632],{"class":1438},"\"### Sending \"",[1397,1634,1596],{"class":1407},[1397,1636,1556],{"class":1403},[1397,1638,1639],{"class":1407},"+",[1397,1641,1642],{"class":1438}," \" ###\"",[1397,1644,1448],{"class":1403},[1397,1646,1647],{"class":1399,"line":1489},[1397,1648,1423],{"emptyLinePlaceholder":234},[1397,1650,1652,1654,1657,1659,1661,1663,1666,1668,1670],{"class":1399,"line":1651},7,[1397,1653,1585],{"class":1407},[1397,1655,1656],{"class":1403}," ProducerRecord\u003C",[1397,1658,1520],{"class":1407},[1397,1660,1442],{"class":1403},[1397,1662,1520],{"class":1407},[1397,1664,1665],{"class":1403},"> record ",[1397,1667,1408],{"class":1407},[1397,1669,1411],{"class":1407},[1397,1671,1672],{"class":1403}," ProducerRecord\u003C>(TOPIC, key, value);\n",[1397,1674,1676,1679,1682],{"class":1399,"line":1675},8,[1397,1677,1678],{"class":1403},"    producer.",[1397,1680,1681],{"class":1414},"send",[1397,1683,1684],{"class":1403},"(record);\n",[1397,1686,1688],{"class":1399,"line":1687},9,[1397,1689,1690],{"class":1403},"}\n",[211,1692,1693,1694],{},"This posts 5 messages to the topic ",[1296,1695,1696],{},"hello-world-topic",[1698,1699,1701],"h3",{"id":1700},"build-and-run-the-producer","Build and run the producer",[211,1703,1704],{},"This is a standard maven project - simply open in your favourite IDE and build.",[211,1706,1707,1708,1713],{},"The BasicProducer class contains a standard main method - so you should easily be able to run the code from the IDE too. You can also use the maven-exec plugin (see ",[1118,1709,1712],{"href":1710,"rel":1711,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Fblob\u002Fmaster\u002Fjava-starter\u002Fproducer\u002FREADME.md",[1122,1123,1124],"the readme",").",[211,1715,1716],{},"The output isn't wildly exciting (we haven't configured logging so ignore related lines):",[1317,1718,1721],{"className":1719,"code":1720,"language":1322},[1320],"*** Starting Basic Producer ***\n### Sending 1 ###\n### Sending 2 ###\n### Sending 3 ###\n### Sending 4 ###\n### Sending 5 ###\n",[1296,1722,1720],{"__ignoreMap":227},[211,1724,1725],{},"So - let's see if we can see what was added to the topic using a consumer.",[1254,1727,1729],{"id":1728},"consumer","Consumer",[211,1731,1732,1733],{},"There is again one main java file here - ",[1118,1734,1737],{"href":1735,"rel":1736,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Fblob\u002Fmaster\u002Fjava-starter\u002Fconsumer\u002Fsrc\u002Fmain\u002Fjava\u002Fnet\u002Fchrissearle\u002Fkafka\u002FBasicConsumer.java",[1122,1123,1124],"BasicConsumer.java",[211,1739,1740],{},"The main method again does three main things:",[1317,1742,1744],{"className":1392,"code":1743,"language":1345,"meta":227,"style":227},"Properties settings = new Properties();\n\nsettings.put(ConsumerConfig.GROUP_ID_CONFIG, \"basic-consumer\");\nsettings.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, \"localhost:29092\");\nsettings.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, \"true\");\nsettings.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, \"1000\");\nsettings.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, \"earliest\");\nsettings.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);\nsettings.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);\n",[1296,1745,1746,1758,1762,1776,1789,1803,1817,1831,1840],{"__ignoreMap":227},[1397,1747,1748,1750,1752,1754,1756],{"class":1399,"line":1400},[1397,1749,1404],{"class":1403},[1397,1751,1408],{"class":1407},[1397,1753,1411],{"class":1407},[1397,1755,1415],{"class":1414},[1397,1757,1418],{"class":1403},[1397,1759,1760],{"class":1399,"line":228},[1397,1761,1423],{"emptyLinePlaceholder":234},[1397,1763,1764,1766,1768,1771,1774],{"class":1399,"line":1426},[1397,1765,1429],{"class":1403},[1397,1767,1432],{"class":1414},[1397,1769,1770],{"class":1403},"(ConsumerConfig.GROUP_ID_CONFIG, ",[1397,1772,1773],{"class":1438},"\"basic-consumer\"",[1397,1775,1448],{"class":1403},[1397,1777,1778,1780,1782,1785,1787],{"class":1399,"line":1451},[1397,1779,1429],{"class":1403},[1397,1781,1432],{"class":1414},[1397,1783,1784],{"class":1403},"(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, ",[1397,1786,1465],{"class":1438},[1397,1788,1448],{"class":1403},[1397,1790,1791,1793,1795,1798,1801],{"class":1399,"line":1470},[1397,1792,1429],{"class":1403},[1397,1794,1432],{"class":1414},[1397,1796,1797],{"class":1403},"(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, ",[1397,1799,1800],{"class":1438},"\"true\"",[1397,1802,1448],{"class":1403},[1397,1804,1805,1807,1809,1812,1815],{"class":1399,"line":1489},[1397,1806,1429],{"class":1403},[1397,1808,1432],{"class":1414},[1397,1810,1811],{"class":1403},"(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, ",[1397,1813,1814],{"class":1438},"\"1000\"",[1397,1816,1448],{"class":1403},[1397,1818,1819,1821,1823,1826,1829],{"class":1399,"line":1651},[1397,1820,1429],{"class":1403},[1397,1822,1432],{"class":1414},[1397,1824,1825],{"class":1403},"(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, ",[1397,1827,1828],{"class":1438},"\"earliest\"",[1397,1830,1448],{"class":1403},[1397,1832,1833,1835,1837],{"class":1399,"line":1675},[1397,1834,1429],{"class":1403},[1397,1836,1432],{"class":1414},[1397,1838,1839],{"class":1403},"(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);\n",[1397,1841,1842,1844,1846],{"class":1399,"line":1687},[1397,1843,1429],{"class":1403},[1397,1845,1432],{"class":1414},[1397,1847,1848],{"class":1403},"(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);\n",[211,1850,1851],{},"This sets up the configuration of the kafka consumer that we want and says we are working with records that use String both as key and value.",[1317,1853,1855],{"className":1392,"code":1854,"language":1345,"meta":227,"style":227},"KafkaConsumer\u003CString, String> consumer = new KafkaConsumer\u003C>(settings);\n",[1296,1856,1857],{"__ignoreMap":227},[1397,1858,1859,1862,1864,1866,1868,1871,1873,1875],{"class":1399,"line":1400},[1397,1860,1861],{"class":1403},"KafkaConsumer\u003C",[1397,1863,1520],{"class":1407},[1397,1865,1442],{"class":1403},[1397,1867,1520],{"class":1407},[1397,1869,1870],{"class":1403},"> consumer ",[1397,1872,1408],{"class":1407},[1397,1874,1411],{"class":1407},[1397,1876,1877],{"class":1403}," KafkaConsumer\u003C>(settings);\n",[211,1879,1880],{},"This creates the consumer with the supplied configuration.",[1317,1882,1884],{"className":1392,"code":1883,"language":1345,"meta":227,"style":227},"consumer.subscribe(Collections.singletonList(\"hello-world-topic\"));\n\nwhile (true) {\n    ConsumerRecords\u003CString, String> records = consumer.poll(Duration.ofMillis(100));\n\n    for (ConsumerRecord\u003CString, String> record : records) {\n        System.out.printf(\"offset = %d, key = %s, value = %s%n\", record.offset(), record.key(),record.value());\n    }\n}\n",[1296,1885,1886,1908,1912,1924,1959,1963,1985,2019,2024],{"__ignoreMap":227},[1397,1887,1888,1891,1894,1897,1900,1902,1905],{"class":1399,"line":1400},[1397,1889,1890],{"class":1403},"consumer.",[1397,1892,1893],{"class":1414},"subscribe",[1397,1895,1896],{"class":1403},"(Collections.",[1397,1898,1899],{"class":1414},"singletonList",[1397,1901,1435],{"class":1403},[1397,1903,1904],{"class":1438},"\"hello-world-topic\"",[1397,1906,1907],{"class":1403},"));\n",[1397,1909,1910],{"class":1399,"line":228},[1397,1911,1423],{"emptyLinePlaceholder":234},[1397,1913,1914,1917,1919,1922],{"class":1399,"line":1426},[1397,1915,1916],{"class":1407},"while",[1397,1918,1550],{"class":1403},[1397,1920,1921],{"class":1561},"true",[1397,1923,1580],{"class":1403},[1397,1925,1926,1929,1931,1933,1935,1938,1940,1943,1946,1949,1952,1954,1957],{"class":1399,"line":1451},[1397,1927,1928],{"class":1403},"    ConsumerRecords\u003C",[1397,1930,1520],{"class":1407},[1397,1932,1442],{"class":1403},[1397,1934,1520],{"class":1407},[1397,1936,1937],{"class":1403},"> records ",[1397,1939,1408],{"class":1407},[1397,1941,1942],{"class":1403}," consumer.",[1397,1944,1945],{"class":1414},"poll",[1397,1947,1948],{"class":1403},"(Duration.",[1397,1950,1951],{"class":1414},"ofMillis",[1397,1953,1435],{"class":1403},[1397,1955,1956],{"class":1561},"100",[1397,1958,1907],{"class":1403},[1397,1960,1961],{"class":1399,"line":1470},[1397,1962,1423],{"emptyLinePlaceholder":234},[1397,1964,1965,1968,1971,1973,1975,1977,1979,1982],{"class":1399,"line":1489},[1397,1966,1967],{"class":1407},"    for",[1397,1969,1970],{"class":1403}," (ConsumerRecord\u003C",[1397,1972,1520],{"class":1407},[1397,1974,1442],{"class":1403},[1397,1976,1520],{"class":1407},[1397,1978,1665],{"class":1403},[1397,1980,1981],{"class":1407},":",[1397,1983,1984],{"class":1403}," records) {\n",[1397,1986,1987,1990,1993,1995,1998,2001,2004,2007,2010,2013,2016],{"class":1399,"line":1651},[1397,1988,1989],{"class":1403},"        System.out.",[1397,1991,1992],{"class":1414},"printf",[1397,1994,1435],{"class":1403},[1397,1996,1997],{"class":1438},"\"offset = %d, key = %s, value = %s%n\"",[1397,1999,2000],{"class":1403},", record.",[1397,2002,2003],{"class":1414},"offset",[1397,2005,2006],{"class":1403},"(), record.",[1397,2008,2009],{"class":1414},"key",[1397,2011,2012],{"class":1403},"(),record.",[1397,2014,2015],{"class":1414},"value",[1397,2017,2018],{"class":1403},"());\n",[1397,2020,2021],{"class":1399,"line":1675},[1397,2022,2023],{"class":1403},"    }\n",[1397,2025,2026],{"class":1399,"line":1687},[1397,2027,1690],{"class":1403},[211,2029,2030,2031,2033],{},"This subscribes to the topic ",[1296,2032,1696],{}," and prints out whatever it finds there.",[1698,2035,2037],{"id":2036},"build-and-run-the-consumer","Build and run the consumer",[211,2039,2040,2041,1133],{},"This is also a standard maven project - again simply open in your favourite IDE and build then run the main method in BasicConsumer or use ",[1118,2042,2045],{"href":2043,"rel":2044,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Fblob\u002Fmaster\u002Fjava-starter\u002Fconsumer\u002FREADME.md",[1122,1123,1124],"maven directly from the command line",[211,2047,2048],{},"The consumer output now shows the messages that the basic producer sent to the topic:",[1317,2050,2053],{"className":2051,"code":2052,"language":1322},[1320],"*** Starting Basic Consumer ***\noffset = 0, key = key-1, value = value-1\noffset = 1, key = key-2, value = value-2\noffset = 2, key = key-3, value = value-3\noffset = 3, key = key-4, value = value-4\noffset = 4, key = key-5, value = value-5\n",[1296,2054,2052],{"__ignoreMap":227},[211,2056,2057],{},"Since the consumer listens until interrupted - break out with Ctrl-C.",[1254,2059,2061],{"id":2060},"summary","Summary",[211,2063,2064],{},"So - we have now a basic consumer and producer in java. Our next step will be a basic scala variant.",[2066,2067,2068],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":227,"searchDepth":228,"depth":228,"links":2070},[2071,2072,2075,2078],{"id":1365,"depth":228,"text":1366},{"id":1377,"depth":228,"text":1378,"children":2073},[2074],{"id":1700,"depth":1426,"text":1701},{"id":1728,"depth":228,"text":1729,"children":2076},[2077],{"id":2036,"depth":1426,"text":2037},{"id":2060,"depth":228,"text":2061},"2019-04-25 12:14 +0200","Basic producer and consumer in java",{"updated":1337},"\u002F2019\u002F04\u002F25\u002Fkafka-java-to-scala-java",{"title":1350,"description":1355},{"loc":2082},"2019\u002F04\u002F25\u002Fkafka-java-to-scala-java",[1344,1345,1377,1728,2087],"tutorial","F2qwcVZhLHuYHo_tE1IlSQ7nt0MQmm1hAxgMCOyyz08",{"id":2090,"title":2091,"body":2092,"category":27,"date":3547,"description":1355,"embedImage":231,"extension":232,"image":231,"intro":2098,"meta":3548,"navigation":234,"path":3549,"seo":3550,"series":1340,"sitemap":3551,"stem":3552,"tags":3553,"__hash__":3554},"content\u002F2019\u002F04\u002F30\u002Fkafka-java-to-scala-scala-v1.md","Kafka - java to scala - scala v1 - basic",{"type":208,"value":2093,"toc":3535},[2094,2096,2099,2101,2105,2108,2111,2116,2119,2124,2130,2133,2136,2143,2146,2285,2289,2294,2297,2303,2307,2310,2314,2317,2320,2861,2864,2867,2882,2885,2897,2900,2906,2908,2911,2914,2917,2920,2935,2938,2943,2946,2950,2953,3465,3468,3470,3480,3483,3493,3496,3501,3503,3506,3509,3512,3516,3532],[211,2095,1355],{},[211,2097,2098],{},"In the previous step we created a basic producer and consumer in java. Let's try for a direct conversion (almost line by line) to scala as a first step.",[1254,2100,1378],{"id":1377},[1698,2102,2104],{"id":2103},"project-structure","Project Structure",[211,2106,2107],{},"Scala uses sbt as its build tool.",[211,2109,2110],{},"First we create the project structure. We'll use the sbt function that uses a giter8 template to create the project.",[211,2112,2113],{},[1296,2114,2115],{},"sbt new scala\u002Fscala-seed.g8 --name=BasicProducer",[211,2117,2118],{},"This will download a bunch of stuff then create a structure in .\u002Fbasicproducer.",[2120,2121,2123],"h4",{"id":2122},"buildsbt","build.sbt",[211,2125,2126,2127,2129],{},"Now - let's get ",[1296,2128,2123],{}," updated. We need to customize the file so that it works for us.",[211,2131,2132],{},"Firstly - at the time of writing the latest scala was 2.13 - but some dependencies in kafka we'll be using are expecting a 2.12.x - so we'll set the scalaVersion.",[211,2134,2135],{},"I've also updated the organization and dumped the organizationName params.",[211,2137,2138,2139,2142],{},"Finally - we'll drop the Dependencies object (we can delete the file ",[1296,2140,2141],{},"project\u002FDependencies.scala",") - we have such a simple project we don't need a complex dependency setup.",[211,2144,2145],{},"This gives be the following file:",[1317,2147,2150],{"className":2148,"code":2149,"language":1346,"meta":227,"style":227},"language-scala shiki shiki-themes github-dark","ThisBuild \u002F scalaVersion     := \"2.12.10\"\nThisBuild \u002F version          := \"0.1.0-SNAPSHOT\"\nThisBuild \u002F organization     := \"net.chrissearle\"\n\nlazy val root = (project in file(\".\"))\n  .settings(\n    name := \"BasicProducer\",\n    libraryDependencies ++= Seq(\n      \"org.apache.kafka\" % \"kafka-clients\" % \"2.3.0\"\n    )\n  )\n\n",[1296,2151,2152,2169,2183,2197,2201,2225,2230,2243,2257,2273,2279],{"__ignoreMap":227},[1397,2153,2154,2157,2160,2163,2166],{"class":1399,"line":1400},[1397,2155,2156],{"class":1414},"ThisBuild",[1397,2158,2159],{"class":1407}," \u002F",[1397,2161,2162],{"class":1403}," scalaVersion     ",[1397,2164,2165],{"class":1407},":=",[1397,2167,2168],{"class":1438}," \"2.12.10\"\n",[1397,2170,2171,2173,2175,2178,2180],{"class":1399,"line":228},[1397,2172,2156],{"class":1414},[1397,2174,2159],{"class":1407},[1397,2176,2177],{"class":1403}," version          ",[1397,2179,2165],{"class":1407},[1397,2181,2182],{"class":1438}," \"0.1.0-SNAPSHOT\"\n",[1397,2184,2185,2187,2189,2192,2194],{"class":1399,"line":1426},[1397,2186,2156],{"class":1414},[1397,2188,2159],{"class":1407},[1397,2190,2191],{"class":1403}," organization     ",[1397,2193,2165],{"class":1407},[1397,2195,2196],{"class":1438}," \"net.chrissearle\"\n",[1397,2198,2199],{"class":1399,"line":1451},[1397,2200,1423],{"emptyLinePlaceholder":234},[1397,2202,2203,2206,2209,2213,2216,2219,2222],{"class":1399,"line":1470},[1397,2204,2205],{"class":1407},"lazy",[1397,2207,2208],{"class":1407}," val",[1397,2210,2212],{"class":2211},"s9osk"," root",[1397,2214,2215],{"class":1407}," =",[1397,2217,2218],{"class":1403}," (project in file(",[1397,2220,2221],{"class":1438},"\".\"",[1397,2223,2224],{"class":1403},"))\n",[1397,2226,2227],{"class":1399,"line":1489},[1397,2228,2229],{"class":1403},"  .settings(\n",[1397,2231,2232,2235,2237,2240],{"class":1399,"line":1651},[1397,2233,2234],{"class":1403},"    name ",[1397,2236,2165],{"class":1407},[1397,2238,2239],{"class":1438}," \"BasicProducer\"",[1397,2241,2242],{"class":1403},",\n",[1397,2244,2245,2248,2251,2254],{"class":1399,"line":1675},[1397,2246,2247],{"class":1403},"    libraryDependencies ",[1397,2249,2250],{"class":1407},"++=",[1397,2252,2253],{"class":1414}," Seq",[1397,2255,2256],{"class":1403},"(\n",[1397,2258,2259,2262,2265,2268,2270],{"class":1399,"line":1687},[1397,2260,2261],{"class":1438},"      \"org.apache.kafka\"",[1397,2263,2264],{"class":1407}," %",[1397,2266,2267],{"class":1438}," \"kafka-clients\"",[1397,2269,2264],{"class":1407},[1397,2271,2272],{"class":1438}," \"2.3.0\"\n",[1397,2274,2276],{"class":1399,"line":2275},10,[1397,2277,2278],{"class":1403},"    )\n",[1397,2280,2282],{"class":1399,"line":2281},11,[1397,2283,2284],{"class":1403},"  )\n",[2120,2286,2288],{"id":2287},"projectbuildproperties","project\u002Fbuild.properties",[211,2290,2291,2292,1133],{},"We configure which sbt version we want in the file ",[1296,2293,2288],{},[211,2295,2296],{},"The generated version is fine:",[1317,2298,2301],{"className":2299,"code":2300,"language":1322},[1320],"sbt.version=1.3.2\n",[1296,2302,2300],{"__ignoreMap":227},[1698,2304,2306],{"id":2305},"project-code","Project Code",[211,2308,2309],{},"The template created the directory structure we want under src\u002Fmain. Let's remove the src\u002Fmain\u002Fscala\u002Fexample\u002FHello.scala file - we don't need that. For now we'll also remove the src\u002Ftest directory - naughty I know - but there are plenty of other scala testing tutorials out there.",[2120,2311,2313],{"id":2312},"scala-producer","Scala producer",[211,2315,2316],{},"Now for the actual scala code.",[211,2318,2319],{},"We'll create BasicProducer.scala in the src\u002Fmain\u002Fscala directory.",[1317,2321,2323],{"className":2148,"code":2322,"language":1346,"meta":227,"style":227},"import java.time.Duration\nimport java.util.Properties\n\nimport org.apache.kafka.clients.producer.ProducerConfig.{BOOTSTRAP_SERVERS_CONFIG, CLIENT_ID_CONFIG, KEY_SERIALIZER_CLASS_CONFIG, VALUE_SERIALIZER_CLASS_CONFIG}\nimport org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}\nimport org.apache.kafka.common.serialization.StringSerializer\n\nobject BasicProducer {\n\n  def main(args: Array[String]): Unit = {\n\n    println(\"*** Starting Basic Producer ***\")\n\n    val settings = new Properties()\n\n    settings.put(CLIENT_ID_CONFIG, \"basic-producer\")\n    settings.put(BOOTSTRAP_SERVERS_CONFIG, \"localhost:29092\")\n    settings.put(KEY_SERIALIZER_CLASS_CONFIG, classOf[StringSerializer].getCanonicalName)\n    settings.put(VALUE_SERIALIZER_CLASS_CONFIG, classOf[StringSerializer].getCanonicalName)\n\n    val producer = new KafkaProducer[String, String](settings)\n\n    val topic = \"scala-v1-basic-topic\"\n\n    for (i \u003C- 1 to 5) {\n      val key = \"key-\" + i\n      val value = \"value-\" + i\n\n      println(s\"### Sending ${i} ###\")\n\n      producer.send(new ProducerRecord[String, String](topic, key, value))\n    }\n\n    producer.close(Duration.ofMillis(100))\n\n    println(\"### Stopping Basic Producer ###\")\n\n  }\n}\n",[1296,2324,2325,2343,2359,2363,2416,2450,2479,2483,2494,2498,2534,2538,2550,2555,2573,2578,2592,2605,2625,2642,2647,2673,2678,2691,2696,2717,2735,2751,2756,2776,2781,2804,2809,2814,2830,2835,2845,2850,2856],{"__ignoreMap":227},[1397,2326,2327,2330,2333,2335,2338,2340],{"class":1399,"line":1400},[1397,2328,2329],{"class":1407},"import",[1397,2331,2332],{"class":1414}," java",[1397,2334,1133],{"class":1403},[1397,2336,2337],{"class":1414},"time",[1397,2339,1133],{"class":1403},[1397,2341,2342],{"class":1414},"Duration\n",[1397,2344,2345,2347,2349,2351,2354,2356],{"class":1399,"line":228},[1397,2346,2329],{"class":1407},[1397,2348,2332],{"class":1414},[1397,2350,1133],{"class":1403},[1397,2352,2353],{"class":1414},"util",[1397,2355,1133],{"class":1403},[1397,2357,2358],{"class":1414},"Properties\n",[1397,2360,2361],{"class":1399,"line":1426},[1397,2362,1423],{"emptyLinePlaceholder":234},[1397,2364,2365,2367,2370,2372,2375,2377,2379,2381,2384,2386,2388,2390,2393,2396,2399,2401,2404,2406,2409,2411,2414],{"class":1399,"line":1451},[1397,2366,2329],{"class":1407},[1397,2368,2369],{"class":1414}," org",[1397,2371,1133],{"class":1403},[1397,2373,2374],{"class":1414},"apache",[1397,2376,1133],{"class":1403},[1397,2378,1344],{"class":1414},[1397,2380,1133],{"class":1403},[1397,2382,2383],{"class":1414},"clients",[1397,2385,1133],{"class":1403},[1397,2387,1377],{"class":1414},[1397,2389,1133],{"class":1403},[1397,2391,2392],{"class":1414},"ProducerConfig",[1397,2394,2395],{"class":1403},".{",[1397,2397,2398],{"class":1414},"BOOTSTRAP_SERVERS_CONFIG",[1397,2400,1442],{"class":1403},[1397,2402,2403],{"class":1414},"CLIENT_ID_CONFIG",[1397,2405,1442],{"class":1403},[1397,2407,2408],{"class":1414},"KEY_SERIALIZER_CLASS_CONFIG",[1397,2410,1442],{"class":1403},[1397,2412,2413],{"class":1414},"VALUE_SERIALIZER_CLASS_CONFIG",[1397,2415,1690],{"class":1403},[1397,2417,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2443,2445,2448],{"class":1399,"line":1470},[1397,2419,2329],{"class":1407},[1397,2421,2369],{"class":1414},[1397,2423,1133],{"class":1403},[1397,2425,2374],{"class":1414},[1397,2427,1133],{"class":1403},[1397,2429,1344],{"class":1414},[1397,2431,1133],{"class":1403},[1397,2433,2383],{"class":1414},[1397,2435,1133],{"class":1403},[1397,2437,1377],{"class":1414},[1397,2439,2395],{"class":1403},[1397,2441,2442],{"class":1414},"KafkaProducer",[1397,2444,1442],{"class":1403},[1397,2446,2447],{"class":1414},"ProducerRecord",[1397,2449,1690],{"class":1403},[1397,2451,2452,2454,2456,2458,2460,2462,2464,2466,2469,2471,2474,2476],{"class":1399,"line":1489},[1397,2453,2329],{"class":1407},[1397,2455,2369],{"class":1414},[1397,2457,1133],{"class":1403},[1397,2459,2374],{"class":1414},[1397,2461,1133],{"class":1403},[1397,2463,1344],{"class":1414},[1397,2465,1133],{"class":1403},[1397,2467,2468],{"class":1414},"common",[1397,2470,1133],{"class":1403},[1397,2472,2473],{"class":1414},"serialization",[1397,2475,1133],{"class":1403},[1397,2477,2478],{"class":1414},"StringSerializer\n",[1397,2480,2481],{"class":1399,"line":1651},[1397,2482,1423],{"emptyLinePlaceholder":234},[1397,2484,2485,2488,2491],{"class":1399,"line":1675},[1397,2486,2487],{"class":1407},"object",[1397,2489,2490],{"class":1414}," BasicProducer",[1397,2492,2493],{"class":1403}," {\n",[1397,2495,2496],{"class":1399,"line":1687},[1397,2497,1423],{"emptyLinePlaceholder":234},[1397,2499,2500,2503,2506,2508,2511,2514,2517,2520,2522,2525,2527,2530,2532],{"class":1399,"line":2275},[1397,2501,2502],{"class":1407},"  def",[1397,2504,2505],{"class":1414}," main",[1397,2507,1435],{"class":1403},[1397,2509,2510],{"class":2211},"args",[1397,2512,2513],{"class":1403},": ",[1397,2515,2516],{"class":1414},"Array",[1397,2518,2519],{"class":1403},"[",[1397,2521,1520],{"class":1414},[1397,2523,2524],{"class":1403},"])",[1397,2526,1981],{"class":1407},[1397,2528,2529],{"class":1414}," Unit",[1397,2531,2215],{"class":1407},[1397,2533,2493],{"class":1403},[1397,2535,2536],{"class":1399,"line":2281},[1397,2537,1423],{"emptyLinePlaceholder":234},[1397,2539,2541,2544,2547],{"class":1399,"line":2540},12,[1397,2542,2543],{"class":1403},"    println(",[1397,2545,2546],{"class":1438},"\"*** Starting Basic Producer ***\"",[1397,2548,2549],{"class":1403},")\n",[1397,2551,2553],{"class":1399,"line":2552},13,[1397,2554,1423],{"emptyLinePlaceholder":234},[1397,2556,2558,2561,2564,2566,2568,2570],{"class":1399,"line":2557},14,[1397,2559,2560],{"class":1407},"    val",[1397,2562,2563],{"class":2211}," settings",[1397,2565,2215],{"class":1407},[1397,2567,1411],{"class":1407},[1397,2569,1415],{"class":1414},[1397,2571,2572],{"class":1403},"()\n",[1397,2574,2576],{"class":1399,"line":2575},15,[1397,2577,1423],{"emptyLinePlaceholder":234},[1397,2579,2581,2584,2586,2588,2590],{"class":1399,"line":2580},16,[1397,2582,2583],{"class":1403},"    settings.put(",[1397,2585,2403],{"class":1414},[1397,2587,1442],{"class":1403},[1397,2589,1445],{"class":1438},[1397,2591,2549],{"class":1403},[1397,2593,2595,2597,2599,2601,2603],{"class":1399,"line":2594},17,[1397,2596,2583],{"class":1403},[1397,2598,2398],{"class":1414},[1397,2600,1442],{"class":1403},[1397,2602,1465],{"class":1438},[1397,2604,2549],{"class":1403},[1397,2606,2608,2610,2612,2614,2617,2619,2622],{"class":1399,"line":2607},18,[1397,2609,2583],{"class":1403},[1397,2611,2408],{"class":1414},[1397,2613,1442],{"class":1403},[1397,2615,2616],{"class":1561},"classOf",[1397,2618,2519],{"class":1403},[1397,2620,2621],{"class":1414},"StringSerializer",[1397,2623,2624],{"class":1403},"].getCanonicalName)\n",[1397,2626,2628,2630,2632,2634,2636,2638,2640],{"class":1399,"line":2627},19,[1397,2629,2583],{"class":1403},[1397,2631,2413],{"class":1414},[1397,2633,1442],{"class":1403},[1397,2635,2616],{"class":1561},[1397,2637,2519],{"class":1403},[1397,2639,2621],{"class":1414},[1397,2641,2624],{"class":1403},[1397,2643,2645],{"class":1399,"line":2644},20,[1397,2646,1423],{"emptyLinePlaceholder":234},[1397,2648,2650,2652,2655,2657,2659,2662,2664,2666,2668,2670],{"class":1399,"line":2649},21,[1397,2651,2560],{"class":1407},[1397,2653,2654],{"class":2211}," producer",[1397,2656,2215],{"class":1407},[1397,2658,1411],{"class":1407},[1397,2660,2661],{"class":1414}," KafkaProducer",[1397,2663,2519],{"class":1403},[1397,2665,1520],{"class":1414},[1397,2667,1442],{"class":1403},[1397,2669,1520],{"class":1414},[1397,2671,2672],{"class":1403},"](settings)\n",[1397,2674,2676],{"class":1399,"line":2675},22,[1397,2677,1423],{"emptyLinePlaceholder":234},[1397,2679,2681,2683,2686,2688],{"class":1399,"line":2680},23,[1397,2682,2560],{"class":1407},[1397,2684,2685],{"class":2211}," topic",[1397,2687,2215],{"class":1407},[1397,2689,2690],{"class":1438}," \"scala-v1-basic-topic\"\n",[1397,2692,2694],{"class":1399,"line":2693},24,[1397,2695,1423],{"emptyLinePlaceholder":234},[1397,2697,2699,2701,2704,2707,2709,2712,2715],{"class":1399,"line":2698},25,[1397,2700,1967],{"class":1407},[1397,2702,2703],{"class":1403}," (i ",[1397,2705,2706],{"class":1407},"\u003C-",[1397,2708,1562],{"class":1561},[1397,2710,2711],{"class":1403}," to ",[1397,2713,2714],{"class":1561},"5",[1397,2716,1580],{"class":1403},[1397,2718,2720,2723,2726,2728,2730,2732],{"class":1399,"line":2719},26,[1397,2721,2722],{"class":1407},"      val",[1397,2724,2725],{"class":2211}," key",[1397,2727,2215],{"class":1407},[1397,2729,1593],{"class":1438},[1397,2731,1596],{"class":1407},[1397,2733,2734],{"class":1403}," i\n",[1397,2736,2738,2740,2743,2745,2747,2749],{"class":1399,"line":2737},27,[1397,2739,2722],{"class":1407},[1397,2741,2742],{"class":2211}," value",[1397,2744,2215],{"class":1407},[1397,2746,1611],{"class":1438},[1397,2748,1596],{"class":1407},[1397,2750,2734],{"class":1403},[1397,2752,2754],{"class":1399,"line":2753},28,[1397,2755,1423],{"emptyLinePlaceholder":234},[1397,2757,2759,2762,2765,2768,2771,2774],{"class":1399,"line":2758},29,[1397,2760,2761],{"class":1403},"      println(",[1397,2763,2764],{"class":1407},"s",[1397,2766,2767],{"class":1438},"\"### Sending ",[1397,2769,2770],{"class":1403},"${i}",[1397,2772,2773],{"class":1438}," ###\"",[1397,2775,2549],{"class":1403},[1397,2777,2779],{"class":1399,"line":2778},30,[1397,2780,1423],{"emptyLinePlaceholder":234},[1397,2782,2784,2787,2790,2793,2795,2797,2799,2801],{"class":1399,"line":2783},31,[1397,2785,2786],{"class":1403},"      producer.send(",[1397,2788,2789],{"class":1407},"new",[1397,2791,2792],{"class":1414}," ProducerRecord",[1397,2794,2519],{"class":1403},[1397,2796,1520],{"class":1414},[1397,2798,1442],{"class":1403},[1397,2800,1520],{"class":1414},[1397,2802,2803],{"class":1403},"](topic, key, value))\n",[1397,2805,2807],{"class":1399,"line":2806},32,[1397,2808,2023],{"class":1403},[1397,2810,2812],{"class":1399,"line":2811},33,[1397,2813,1423],{"emptyLinePlaceholder":234},[1397,2815,2817,2820,2823,2826,2828],{"class":1399,"line":2816},34,[1397,2818,2819],{"class":1403},"    producer.close(",[1397,2821,2822],{"class":1414},"Duration",[1397,2824,2825],{"class":1403},".ofMillis(",[1397,2827,1956],{"class":1561},[1397,2829,2224],{"class":1403},[1397,2831,2833],{"class":1399,"line":2832},35,[1397,2834,1423],{"emptyLinePlaceholder":234},[1397,2836,2838,2840,2843],{"class":1399,"line":2837},36,[1397,2839,2543],{"class":1403},[1397,2841,2842],{"class":1438},"\"### Stopping Basic Producer ###\"",[1397,2844,2549],{"class":1403},[1397,2846,2848],{"class":1399,"line":2847},37,[1397,2849,1423],{"emptyLinePlaceholder":234},[1397,2851,2853],{"class":1399,"line":2852},38,[1397,2854,2855],{"class":1403},"  }\n",[1397,2857,2859],{"class":1399,"line":2858},39,[1397,2860,1690],{"class":1403},[211,2862,2863],{},"If you compare this to the java version - this is almost line for line the same code.",[211,2865,2866],{},"Let's make sure it compiles:",[1317,2868,2872],{"className":2869,"code":2870,"language":2871,"meta":227,"style":227},"language-shell shiki shiki-themes github-dark","sbt compile\n","shell",[1296,2873,2874],{"__ignoreMap":227},[1397,2875,2876,2879],{"class":1399,"line":1400},[1397,2877,2878],{"class":1414},"sbt",[1397,2880,2881],{"class":1438}," compile\n",[211,2883,2884],{},"And then - let's run it. sbt's run will just run the first main method it finds.",[1317,2886,2888],{"className":2869,"code":2887,"language":2871,"meta":227,"style":227},"sbt run\n",[1296,2889,2890],{"__ignoreMap":227},[1397,2891,2892,2894],{"class":1399,"line":1400},[1397,2893,2878],{"class":1414},[1397,2895,2896],{"class":1438}," run\n",[211,2898,2899],{},"The output here is almost the same as for the java example (we still haven't configured logging so ignore related lines):",[1317,2901,2904],{"className":2902,"code":2903,"language":1322},[1320],"*** Starting Basic Producer ***\n### Sending 1 ###\n### Sending 2 ###\n### Sending 3 ###\n### Sending 4 ###\n### Sending 5 ###\n### Stopping Basic Producer ###\n",[1296,2905,2903],{"__ignoreMap":227},[1254,2907,1729],{"id":1728},[1698,2909,2104],{"id":2910},"project-structure-1",[211,2912,2913],{},"We will use almost the same project structure for the consumer as for the producer.",[2120,2915,2123],{"id":2916},"buildsbt-1",[211,2918,2919],{},"The only difference in build.sbt is the name:",[1317,2921,2923],{"className":2148,"code":2922,"language":1346,"meta":227,"style":227},"name := \"BasicConsumer\"\n",[1296,2924,2925],{"__ignoreMap":227},[1397,2926,2927,2930,2932],{"class":1399,"line":1400},[1397,2928,2929],{"class":1403},"name ",[1397,2931,2165],{"class":1407},[1397,2933,2934],{"class":1438}," \"BasicConsumer\"\n",[2120,2936,2937],{"id":2937},"project",[211,2939,2940,2942],{},[1296,2941,2288],{}," is the same as for producer.",[1698,2944,2306],{"id":2945},"project-code-1",[2120,2947,2949],{"id":2948},"scala-consumer","Scala consumer",[211,2951,2952],{},"We'll create BasicConsumer.scala in the src\u002Fmain\u002Fscala directory.",[1317,2954,2956],{"className":2148,"code":2955,"language":1346,"meta":227,"style":227},"import java.time.Duration\nimport java.util.Properties\n\nimport org.apache.kafka.clients.consumer.ConsumerConfig._\nimport org.apache.kafka.clients.consumer.KafkaConsumer\nimport org.apache.kafka.common.serialization.StringDeserializer\n\nimport collection.JavaConverters._\n\nobject BasicConsumer {\n\n  def main(args: Array[String]): Unit = {\n\n    println(\"*** Starting Basic Consumer ***\")\n\n    val settings = new Properties()\n\n    settings.put(GROUP_ID_CONFIG, \"basic-consumer\")\n    settings.put(BOOTSTRAP_SERVERS_CONFIG, \"localhost:29092\")\n    settings.put(ENABLE_AUTO_COMMIT_CONFIG, \"true\")\n    settings.put(AUTO_COMMIT_INTERVAL_MS_CONFIG, \"1000\")\n    settings.put(AUTO_OFFSET_RESET_CONFIG, \"earliest\")\n    settings.put(KEY_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer])\n    settings.put(VALUE_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer])\n\n    val consumer = new KafkaConsumer[String, String](settings)\n\n    val topic = \"scala-v1-basic-topic\"\n\n    try {\n      consumer.subscribe(List(topic).asJava)\n\n      while (true) {\n        val records = consumer.poll(Duration.ofMillis(100))\n\n        for (record \u003C- records.asScala) {\n          println(s\"offset = ${record.offset}, key = ${record.key}, value = ${record.value}\")\n        }\n      }\n    } finally {\n      consumer.close()\n    }\n  }\n}\n",[1296,2957,2958,2972,2986,2990,3022,3049,3076,3080,3096,3100,3109,3113,3141,3145,3154,3158,3172,3176,3189,3201,3214,3227,3240,3259,3276,3280,3304,3308,3318,3322,3329,3340,3344,3355,3376,3380,3393,3423,3428,3433,3444,3450,3455,3460],{"__ignoreMap":227},[1397,2959,2960,2962,2964,2966,2968,2970],{"class":1399,"line":1400},[1397,2961,2329],{"class":1407},[1397,2963,2332],{"class":1414},[1397,2965,1133],{"class":1403},[1397,2967,2337],{"class":1414},[1397,2969,1133],{"class":1403},[1397,2971,2342],{"class":1414},[1397,2973,2974,2976,2978,2980,2982,2984],{"class":1399,"line":228},[1397,2975,2329],{"class":1407},[1397,2977,2332],{"class":1414},[1397,2979,1133],{"class":1403},[1397,2981,2353],{"class":1414},[1397,2983,1133],{"class":1403},[1397,2985,2358],{"class":1414},[1397,2987,2988],{"class":1399,"line":1426},[1397,2989,1423],{"emptyLinePlaceholder":234},[1397,2991,2992,2994,2996,2998,3000,3002,3004,3006,3008,3010,3012,3014,3017,3019],{"class":1399,"line":1451},[1397,2993,2329],{"class":1407},[1397,2995,2369],{"class":1414},[1397,2997,1133],{"class":1403},[1397,2999,2374],{"class":1414},[1397,3001,1133],{"class":1403},[1397,3003,1344],{"class":1414},[1397,3005,1133],{"class":1403},[1397,3007,2383],{"class":1414},[1397,3009,1133],{"class":1403},[1397,3011,1728],{"class":1414},[1397,3013,1133],{"class":1403},[1397,3015,3016],{"class":1414},"ConsumerConfig",[1397,3018,1133],{"class":1403},[1397,3020,3021],{"class":1414},"_\n",[1397,3023,3024,3026,3028,3030,3032,3034,3036,3038,3040,3042,3044,3046],{"class":1399,"line":1470},[1397,3025,2329],{"class":1407},[1397,3027,2369],{"class":1414},[1397,3029,1133],{"class":1403},[1397,3031,2374],{"class":1414},[1397,3033,1133],{"class":1403},[1397,3035,1344],{"class":1414},[1397,3037,1133],{"class":1403},[1397,3039,2383],{"class":1414},[1397,3041,1133],{"class":1403},[1397,3043,1728],{"class":1414},[1397,3045,1133],{"class":1403},[1397,3047,3048],{"class":1414},"KafkaConsumer\n",[1397,3050,3051,3053,3055,3057,3059,3061,3063,3065,3067,3069,3071,3073],{"class":1399,"line":1489},[1397,3052,2329],{"class":1407},[1397,3054,2369],{"class":1414},[1397,3056,1133],{"class":1403},[1397,3058,2374],{"class":1414},[1397,3060,1133],{"class":1403},[1397,3062,1344],{"class":1414},[1397,3064,1133],{"class":1403},[1397,3066,2468],{"class":1414},[1397,3068,1133],{"class":1403},[1397,3070,2473],{"class":1414},[1397,3072,1133],{"class":1403},[1397,3074,3075],{"class":1414},"StringDeserializer\n",[1397,3077,3078],{"class":1399,"line":1651},[1397,3079,1423],{"emptyLinePlaceholder":234},[1397,3081,3082,3084,3087,3089,3092,3094],{"class":1399,"line":1675},[1397,3083,2329],{"class":1407},[1397,3085,3086],{"class":1414}," collection",[1397,3088,1133],{"class":1403},[1397,3090,3091],{"class":1414},"JavaConverters",[1397,3093,1133],{"class":1403},[1397,3095,3021],{"class":1414},[1397,3097,3098],{"class":1399,"line":1687},[1397,3099,1423],{"emptyLinePlaceholder":234},[1397,3101,3102,3104,3107],{"class":1399,"line":2275},[1397,3103,2487],{"class":1407},[1397,3105,3106],{"class":1414}," BasicConsumer",[1397,3108,2493],{"class":1403},[1397,3110,3111],{"class":1399,"line":2281},[1397,3112,1423],{"emptyLinePlaceholder":234},[1397,3114,3115,3117,3119,3121,3123,3125,3127,3129,3131,3133,3135,3137,3139],{"class":1399,"line":2540},[1397,3116,2502],{"class":1407},[1397,3118,2505],{"class":1414},[1397,3120,1435],{"class":1403},[1397,3122,2510],{"class":2211},[1397,3124,2513],{"class":1403},[1397,3126,2516],{"class":1414},[1397,3128,2519],{"class":1403},[1397,3130,1520],{"class":1414},[1397,3132,2524],{"class":1403},[1397,3134,1981],{"class":1407},[1397,3136,2529],{"class":1414},[1397,3138,2215],{"class":1407},[1397,3140,2493],{"class":1403},[1397,3142,3143],{"class":1399,"line":2552},[1397,3144,1423],{"emptyLinePlaceholder":234},[1397,3146,3147,3149,3152],{"class":1399,"line":2557},[1397,3148,2543],{"class":1403},[1397,3150,3151],{"class":1438},"\"*** Starting Basic Consumer ***\"",[1397,3153,2549],{"class":1403},[1397,3155,3156],{"class":1399,"line":2575},[1397,3157,1423],{"emptyLinePlaceholder":234},[1397,3159,3160,3162,3164,3166,3168,3170],{"class":1399,"line":2580},[1397,3161,2560],{"class":1407},[1397,3163,2563],{"class":2211},[1397,3165,2215],{"class":1407},[1397,3167,1411],{"class":1407},[1397,3169,1415],{"class":1414},[1397,3171,2572],{"class":1403},[1397,3173,3174],{"class":1399,"line":2594},[1397,3175,1423],{"emptyLinePlaceholder":234},[1397,3177,3178,3180,3183,3185,3187],{"class":1399,"line":2607},[1397,3179,2583],{"class":1403},[1397,3181,3182],{"class":1414},"GROUP_ID_CONFIG",[1397,3184,1442],{"class":1403},[1397,3186,1773],{"class":1438},[1397,3188,2549],{"class":1403},[1397,3190,3191,3193,3195,3197,3199],{"class":1399,"line":2627},[1397,3192,2583],{"class":1403},[1397,3194,2398],{"class":1414},[1397,3196,1442],{"class":1403},[1397,3198,1465],{"class":1438},[1397,3200,2549],{"class":1403},[1397,3202,3203,3205,3208,3210,3212],{"class":1399,"line":2644},[1397,3204,2583],{"class":1403},[1397,3206,3207],{"class":1414},"ENABLE_AUTO_COMMIT_CONFIG",[1397,3209,1442],{"class":1403},[1397,3211,1800],{"class":1438},[1397,3213,2549],{"class":1403},[1397,3215,3216,3218,3221,3223,3225],{"class":1399,"line":2649},[1397,3217,2583],{"class":1403},[1397,3219,3220],{"class":1414},"AUTO_COMMIT_INTERVAL_MS_CONFIG",[1397,3222,1442],{"class":1403},[1397,3224,1814],{"class":1438},[1397,3226,2549],{"class":1403},[1397,3228,3229,3231,3234,3236,3238],{"class":1399,"line":2675},[1397,3230,2583],{"class":1403},[1397,3232,3233],{"class":1414},"AUTO_OFFSET_RESET_CONFIG",[1397,3235,1442],{"class":1403},[1397,3237,1828],{"class":1438},[1397,3239,2549],{"class":1403},[1397,3241,3242,3244,3247,3249,3251,3253,3256],{"class":1399,"line":2680},[1397,3243,2583],{"class":1403},[1397,3245,3246],{"class":1414},"KEY_DESERIALIZER_CLASS_CONFIG",[1397,3248,1442],{"class":1403},[1397,3250,2616],{"class":1561},[1397,3252,2519],{"class":1403},[1397,3254,3255],{"class":1414},"StringDeserializer",[1397,3257,3258],{"class":1403},"])\n",[1397,3260,3261,3263,3266,3268,3270,3272,3274],{"class":1399,"line":2693},[1397,3262,2583],{"class":1403},[1397,3264,3265],{"class":1414},"VALUE_DESERIALIZER_CLASS_CONFIG",[1397,3267,1442],{"class":1403},[1397,3269,2616],{"class":1561},[1397,3271,2519],{"class":1403},[1397,3273,3255],{"class":1414},[1397,3275,3258],{"class":1403},[1397,3277,3278],{"class":1399,"line":2698},[1397,3279,1423],{"emptyLinePlaceholder":234},[1397,3281,3282,3284,3287,3289,3291,3294,3296,3298,3300,3302],{"class":1399,"line":2719},[1397,3283,2560],{"class":1407},[1397,3285,3286],{"class":2211}," consumer",[1397,3288,2215],{"class":1407},[1397,3290,1411],{"class":1407},[1397,3292,3293],{"class":1414}," KafkaConsumer",[1397,3295,2519],{"class":1403},[1397,3297,1520],{"class":1414},[1397,3299,1442],{"class":1403},[1397,3301,1520],{"class":1414},[1397,3303,2672],{"class":1403},[1397,3305,3306],{"class":1399,"line":2737},[1397,3307,1423],{"emptyLinePlaceholder":234},[1397,3309,3310,3312,3314,3316],{"class":1399,"line":2753},[1397,3311,2560],{"class":1407},[1397,3313,2685],{"class":2211},[1397,3315,2215],{"class":1407},[1397,3317,2690],{"class":1438},[1397,3319,3320],{"class":1399,"line":2758},[1397,3321,1423],{"emptyLinePlaceholder":234},[1397,3323,3324,3327],{"class":1399,"line":2778},[1397,3325,3326],{"class":1407},"    try",[1397,3328,2493],{"class":1403},[1397,3330,3331,3334,3337],{"class":1399,"line":2783},[1397,3332,3333],{"class":1403},"      consumer.subscribe(",[1397,3335,3336],{"class":1414},"List",[1397,3338,3339],{"class":1403},"(topic).asJava)\n",[1397,3341,3342],{"class":1399,"line":2806},[1397,3343,1423],{"emptyLinePlaceholder":234},[1397,3345,3346,3349,3351,3353],{"class":1399,"line":2811},[1397,3347,3348],{"class":1407},"      while",[1397,3350,1550],{"class":1403},[1397,3352,1921],{"class":1561},[1397,3354,1580],{"class":1403},[1397,3356,3357,3360,3363,3365,3368,3370,3372,3374],{"class":1399,"line":2816},[1397,3358,3359],{"class":1407},"        val",[1397,3361,3362],{"class":2211}," records",[1397,3364,2215],{"class":1407},[1397,3366,3367],{"class":1403}," consumer.poll(",[1397,3369,2822],{"class":1414},[1397,3371,2825],{"class":1403},[1397,3373,1956],{"class":1561},[1397,3375,2224],{"class":1403},[1397,3377,3378],{"class":1399,"line":2832},[1397,3379,1423],{"emptyLinePlaceholder":234},[1397,3381,3382,3385,3388,3390],{"class":1399,"line":2837},[1397,3383,3384],{"class":1407},"        for",[1397,3386,3387],{"class":1403}," (record ",[1397,3389,2706],{"class":1407},[1397,3391,3392],{"class":1403}," records.asScala) {\n",[1397,3394,3395,3398,3400,3403,3406,3409,3412,3415,3418,3421],{"class":1399,"line":2847},[1397,3396,3397],{"class":1403},"          println(",[1397,3399,2764],{"class":1407},[1397,3401,3402],{"class":1438},"\"offset = ",[1397,3404,3405],{"class":1403},"${record.offset}",[1397,3407,3408],{"class":1438},", key = ",[1397,3410,3411],{"class":1403},"${record.key}",[1397,3413,3414],{"class":1438},", value = ",[1397,3416,3417],{"class":1403},"${record.value}",[1397,3419,3420],{"class":1438},"\"",[1397,3422,2549],{"class":1403},[1397,3424,3425],{"class":1399,"line":2852},[1397,3426,3427],{"class":1403},"        }\n",[1397,3429,3430],{"class":1399,"line":2858},[1397,3431,3432],{"class":1403},"      }\n",[1397,3434,3436,3439,3442],{"class":1399,"line":3435},40,[1397,3437,3438],{"class":1403},"    } ",[1397,3440,3441],{"class":1407},"finally",[1397,3443,2493],{"class":1403},[1397,3445,3447],{"class":1399,"line":3446},41,[1397,3448,3449],{"class":1403},"      consumer.close()\n",[1397,3451,3453],{"class":1399,"line":3452},42,[1397,3454,2023],{"class":1403},[1397,3456,3458],{"class":1399,"line":3457},43,[1397,3459,2855],{"class":1403},[1397,3461,3463],{"class":1399,"line":3462},44,[1397,3464,1690],{"class":1403},[211,3466,3467],{},"If you compare this to the java version - this is again doing the same thing as the java consumer code.",[211,3469,2866],{},[1317,3471,3472],{"className":2869,"code":2870,"language":2871,"meta":227,"style":227},[1296,3473,3474],{"__ignoreMap":227},[1397,3475,3476,3478],{"class":1399,"line":1400},[1397,3477,2878],{"class":1414},[1397,3479,2881],{"class":1438},[211,3481,3482],{},"And then run:",[1317,3484,3485],{"className":2869,"code":2887,"language":2871,"meta":227,"style":227},[1296,3486,3487],{"__ignoreMap":227},[1397,3488,3489,3491],{"class":1399,"line":1400},[1397,3490,2878],{"class":1414},[1397,3492,2896],{"class":1438},[211,3494,3495],{},"The output is the same as for the java example",[1317,3497,3499],{"className":3498,"code":2052,"language":1322},[1320],[1296,3500,2052],{"__ignoreMap":227},[1254,3502,2061],{"id":2060},[211,3504,3505],{},"In this step we converted the producer and consumer to scala.",[211,3507,3508],{},"So far all that has done is to make this a little harder for java coders to understand :)",[211,3510,3511],{},"But moving forward we'll look at improving the code, better scala, akka, streams etc etc.",[1254,3513,3515],{"id":3514},"links","Links",[444,3517,3518,3525],{},[447,3519,3520],{},[1118,3521,3524],{"href":3522,"rel":3523,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fscala-v1-basic\u002Fbasicproducer",[1122,1123,1124],"Producer project",[447,3526,3527],{},[1118,3528,3531],{"href":3529,"rel":3530,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fscala-v1-basic\u002Fbasicconsumer",[1122,1123,1124],"Consumer project",[2066,3533,3534],{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":227,"searchDepth":228,"depth":228,"links":3536},[3537,3541,3545,3546],{"id":1377,"depth":228,"text":1378,"children":3538},[3539,3540],{"id":2103,"depth":1426,"text":2104},{"id":2305,"depth":1426,"text":2306},{"id":1728,"depth":228,"text":1729,"children":3542},[3543,3544],{"id":2910,"depth":1426,"text":2104},{"id":2945,"depth":1426,"text":2306},{"id":2060,"depth":228,"text":2061},{"id":3514,"depth":228,"text":3515},"2019-04-30 12:08 +0200",{"updated":1337},"\u002F2019\u002F04\u002F30\u002Fkafka-java-to-scala-scala-v1",{"title":2091,"description":1355},{"loc":3549},"2019\u002F04\u002F30\u002Fkafka-java-to-scala-scala-v1",[1344,1346,1377,1728],"VbjzaPM2uSar3P0k95EY5D6NBbeypxF1_Irsncl2E2g",{"id":3556,"title":3557,"body":3558,"category":27,"date":5232,"description":1355,"embedImage":231,"extension":232,"image":231,"intro":3564,"meta":5233,"navigation":234,"path":5234,"seo":5235,"series":1340,"sitemap":5236,"stem":5237,"tags":5238,"__hash__":5240},"content\u002F2019\u002F05\u002F03\u002Fkafka-java-to-scala-scala-v2.md","Kafka - java to scala - scala v2 - config",{"type":208,"value":3559,"toc":5215},[3560,3562,3565,3569,3572,3629,3632,3660,3664,3667,3676,3680,3685,3691,3694,3698,3708,3710,3716,3718,3724,3728,3731,3734,3794,3797,3889,3893,3896,3903,3930,3933,4000,4004,4007,4014,4018,4021,4024,4542,4545,5143,5147,5150,5160,5163,5173,5176,5182,5185,5191,5193,5196,5198,5212],[211,3561,1355],{},[211,3563,3564],{},"In the previous step we created a basic producer and consumer in scala but it was very close to a line by line conversion. Let's try for something that is closer to normal scala - and let's get the config values out to a configuration file.",[1254,3566,3568],{"id":3567},"app-boilerplate","App boilerplate",[211,3570,3571],{},"First change - instead of having an object with a main method - let's actually state that it's an app.",[1317,3573,3575],{"className":2148,"code":3574,"language":1346,"meta":227,"style":227},"object X {\n    def main(args: Array[String]): Unit = {\n        \u002F\u002F Application code\n    }\n}\n",[1296,3576,3577,3586,3615,3621,3625],{"__ignoreMap":227},[1397,3578,3579,3581,3584],{"class":1399,"line":1400},[1397,3580,2487],{"class":1407},[1397,3582,3583],{"class":1414}," X",[1397,3585,2493],{"class":1403},[1397,3587,3588,3591,3593,3595,3597,3599,3601,3603,3605,3607,3609,3611,3613],{"class":1399,"line":228},[1397,3589,3590],{"class":1407},"    def",[1397,3592,2505],{"class":1414},[1397,3594,1435],{"class":1403},[1397,3596,2510],{"class":2211},[1397,3598,2513],{"class":1403},[1397,3600,2516],{"class":1414},[1397,3602,2519],{"class":1403},[1397,3604,1520],{"class":1414},[1397,3606,2524],{"class":1403},[1397,3608,1981],{"class":1407},[1397,3610,2529],{"class":1414},[1397,3612,2215],{"class":1407},[1397,3614,2493],{"class":1403},[1397,3616,3617],{"class":1399,"line":1426},[1397,3618,3620],{"class":3619},"sAwPA","        \u002F\u002F Application code\n",[1397,3622,3623],{"class":1399,"line":1451},[1397,3624,2023],{"class":1403},[1397,3626,3627],{"class":1399,"line":1470},[1397,3628,1690],{"class":1403},[211,3630,3631],{},"changes to",[1317,3633,3635],{"className":2148,"code":3634,"language":1346,"meta":227,"style":227},"object X extends App {\n    \u002F\u002F Application code\n}\n",[1296,3636,3637,3651,3656],{"__ignoreMap":227},[1397,3638,3639,3641,3643,3646,3649],{"class":1399,"line":1400},[1397,3640,2487],{"class":1407},[1397,3642,3583],{"class":1414},[1397,3644,3645],{"class":1407}," extends",[1397,3647,3648],{"class":1414}," App",[1397,3650,2493],{"class":1403},[1397,3652,3653],{"class":1399,"line":228},[1397,3654,3655],{"class":3619},"    \u002F\u002F Application code\n",[1397,3657,3658],{"class":1399,"line":1426},[1397,3659,1690],{"class":1403},[1254,3661,3663],{"id":3662},"pure-config","Pure Config",[211,3665,3666],{},"The config values are present in the code - let's do something about that.",[211,3668,3669,3670,3675],{},"For this we'll use the ",[1118,3671,3674],{"href":3672,"rel":3673,"target":1125},"https:\u002F\u002Fpureconfig.github.io\u002F",[1122,1123,1124],"PureConfig"," library.",[1698,3677,3679],{"id":3678},"dependency","Dependency",[211,3681,3682,3683,1981],{},"We need to add the following to ",[1296,3684,2123],{},[1317,3686,3689],{"className":3687,"code":3688,"language":1322},[1320],"  \"com.github.pureconfig\" %% \"pureconfig\" % \"0.12.0\"\n",[1296,3690,3688],{"__ignoreMap":227},[211,3692,3693],{},"This is added to the list of libraryDependencies that is already present.",[1698,3695,3697],{"id":3696},"configuration-file","Configuration file",[211,3699,3700,3701,3704,3705,1133],{},"We can add our configuration values to a file under ",[1296,3702,3703],{},"src\u002Fmain\u002Fresources"," called ",[1296,3706,3707],{},"application.conf",[2120,3709,1378],{"id":1377},[1317,3711,3714],{"className":3712,"code":3713,"language":1322},[1320],"client-id = \"pureconfig-producer\"\nbootstrap-servers = \"localhost:29092\"\ntopic = \"pureconfig-topic\"\nserializer = \"org.apache.kafka.common.serialization.StringSerializer\"\n",[1296,3715,3713],{"__ignoreMap":227},[2120,3717,1729],{"id":1728},[1317,3719,3722],{"className":3720,"code":3721,"language":1322},[1320],"group-id = \"pureconfig-consumer\"\nbootstrap-servers = \"localhost:29092\"\ntopic = \"pureconfig-topic\"\ndeserializer = \"org.apache.kafka.common.serialization.StringDeserializer\"\nenable-auto-commit = \"true\"\nauto-commit-interval-ms = \"1000\"\nauto-offset-reset = \"earliest\"\n",[1296,3723,3721],{"__ignoreMap":227},[1698,3725,3727],{"id":3726},"loading-configuration","Loading configuration",[211,3729,3730],{},"PureConfig can load the values into a matching case class.",[2120,3732,1378],{"id":3733},"producer-1",[1317,3735,3737],{"className":2148,"code":3736,"language":1346,"meta":227,"style":227},"case class Config(clientId: String,\n                  bootstrapServers: String,\n                  topic: String,\n                  serializer: String)\n",[1296,3738,3739,3761,3772,3783],{"__ignoreMap":227},[1397,3740,3741,3744,3747,3750,3752,3755,3757,3759],{"class":1399,"line":1400},[1397,3742,3743],{"class":1407},"case",[1397,3745,3746],{"class":1407}," class",[1397,3748,3749],{"class":1414}," Config",[1397,3751,1435],{"class":1403},[1397,3753,3754],{"class":2211},"clientId",[1397,3756,2513],{"class":1403},[1397,3758,1520],{"class":1414},[1397,3760,2242],{"class":1403},[1397,3762,3763,3766,3768,3770],{"class":1399,"line":228},[1397,3764,3765],{"class":2211},"                  bootstrapServers",[1397,3767,2513],{"class":1403},[1397,3769,1520],{"class":1414},[1397,3771,2242],{"class":1403},[1397,3773,3774,3777,3779,3781],{"class":1399,"line":1426},[1397,3775,3776],{"class":2211},"                  topic",[1397,3778,2513],{"class":1403},[1397,3780,1520],{"class":1414},[1397,3782,2242],{"class":1403},[1397,3784,3785,3788,3790,3792],{"class":1399,"line":1451},[1397,3786,3787],{"class":2211},"                  serializer",[1397,3789,2513],{"class":1403},[1397,3791,1520],{"class":1414},[1397,3793,2549],{"class":1403},[2120,3795,1729],{"id":3796},"consumer-1",[1317,3798,3800],{"className":2148,"code":3799,"language":1346,"meta":227,"style":227},"case class Config(groupId: String,\n                  bootstrapServers: String,\n                  enableAutoCommit: String,\n                  autoCommitIntervalMs: String,\n                  autoOffsetReset: String,\n                  deserializer: String,\n                  topic: String\n                 )\n",[1296,3801,3802,3821,3831,3842,3853,3864,3875,3884],{"__ignoreMap":227},[1397,3803,3804,3806,3808,3810,3812,3815,3817,3819],{"class":1399,"line":1400},[1397,3805,3743],{"class":1407},[1397,3807,3746],{"class":1407},[1397,3809,3749],{"class":1414},[1397,3811,1435],{"class":1403},[1397,3813,3814],{"class":2211},"groupId",[1397,3816,2513],{"class":1403},[1397,3818,1520],{"class":1414},[1397,3820,2242],{"class":1403},[1397,3822,3823,3825,3827,3829],{"class":1399,"line":228},[1397,3824,3765],{"class":2211},[1397,3826,2513],{"class":1403},[1397,3828,1520],{"class":1414},[1397,3830,2242],{"class":1403},[1397,3832,3833,3836,3838,3840],{"class":1399,"line":1426},[1397,3834,3835],{"class":2211},"                  enableAutoCommit",[1397,3837,2513],{"class":1403},[1397,3839,1520],{"class":1414},[1397,3841,2242],{"class":1403},[1397,3843,3844,3847,3849,3851],{"class":1399,"line":1451},[1397,3845,3846],{"class":2211},"                  autoCommitIntervalMs",[1397,3848,2513],{"class":1403},[1397,3850,1520],{"class":1414},[1397,3852,2242],{"class":1403},[1397,3854,3855,3858,3860,3862],{"class":1399,"line":1470},[1397,3856,3857],{"class":2211},"                  autoOffsetReset",[1397,3859,2513],{"class":1403},[1397,3861,1520],{"class":1414},[1397,3863,2242],{"class":1403},[1397,3865,3866,3869,3871,3873],{"class":1399,"line":1489},[1397,3867,3868],{"class":2211},"                  deserializer",[1397,3870,2513],{"class":1403},[1397,3872,1520],{"class":1414},[1397,3874,2242],{"class":1403},[1397,3876,3877,3879,3881],{"class":1399,"line":1651},[1397,3878,3776],{"class":2211},[1397,3880,2513],{"class":1403},[1397,3882,3883],{"class":1414},"String\n",[1397,3885,3886],{"class":1399,"line":1675},[1397,3887,3888],{"class":1403},"                 )\n",[1698,3890,3892],{"id":3891},"loading-successful","Loading successful?",[211,3894,3895],{},"We can choose how to handle a config load so that we know if the configuation was loaded OK or not.",[211,3897,3898,3899,3902],{},"One method we can call is ",[1296,3900,3901],{},"loadOrThrow"," which will throw an exception if it can't load the configuration.",[1317,3904,3906],{"className":2148,"code":3905,"language":1346,"meta":227,"style":227},"val conf = ConfigSource.default.loadOrThrow[Config]\n",[1296,3907,3908],{"__ignoreMap":227},[1397,3909,3910,3913,3916,3918,3921,3924,3927],{"class":1399,"line":1400},[1397,3911,3912],{"class":1407},"val",[1397,3914,3915],{"class":2211}," conf",[1397,3917,2215],{"class":1407},[1397,3919,3920],{"class":1414}," ConfigSource",[1397,3922,3923],{"class":1403},".default.loadOrThrow[",[1397,3925,3926],{"class":1414},"Config",[1397,3928,3929],{"class":1403},"]\n",[211,3931,3932],{},"Another option is to just use load - this returns in effect an Either - where the left choice is ConfigReaderFailures and the right choice is configuration matching the case class asked for.",[1317,3934,3936],{"className":2148,"code":3935,"language":1346,"meta":227,"style":227},"ConfigSource.default.load[Config] match {\n    case Left(errors) => ...\n    case Right(config: Config) => ...\n}\n",[1296,3937,3938,3956,3973,3996],{"__ignoreMap":227},[1397,3939,3940,3943,3946,3948,3951,3954],{"class":1399,"line":1400},[1397,3941,3942],{"class":1414},"ConfigSource",[1397,3944,3945],{"class":1403},".default.load[",[1397,3947,3926],{"class":1414},[1397,3949,3950],{"class":1403},"] ",[1397,3952,3953],{"class":1407},"match",[1397,3955,2493],{"class":1403},[1397,3957,3958,3961,3964,3967,3970],{"class":1399,"line":228},[1397,3959,3960],{"class":1407},"    case",[1397,3962,3963],{"class":1414}," Left",[1397,3965,3966],{"class":1403},"(errors) ",[1397,3968,3969],{"class":1407},"=>",[1397,3971,3972],{"class":1403}," ...\n",[1397,3974,3975,3977,3980,3982,3985,3987,3989,3992,3994],{"class":1399,"line":1426},[1397,3976,3960],{"class":1407},[1397,3978,3979],{"class":1414}," Right",[1397,3981,1435],{"class":1403},[1397,3983,3984],{"class":2211},"config",[1397,3986,2513],{"class":1403},[1397,3988,3926],{"class":1414},[1397,3990,3991],{"class":1403},") ",[1397,3993,3969],{"class":1407},[1397,3995,3972],{"class":1403},[1397,3997,3998],{"class":1399,"line":1451},[1397,3999,1690],{"class":1403},[1698,4001,4003],{"id":4002},"config-asproperties","Config asProperties",[211,4005,4006],{},"There is one thing that means that we can't just use our nice neat Config case classes as is. The kafka clients (KafkaProducer\u002FKafkaConsumer) require a java properties object.",[211,4008,4009,4010,4013],{},"For now - I've simply created an asProperties method onto each Config case class. However - there are other ways of handling this conversion (usually the word ",[1296,4011,4012],{},"implicit"," turns up here - but for this example - let's keep it simple).",[1254,4015,4017],{"id":4016},"updated-clients","Updated clients",[211,4019,4020],{},"We'll throw in some other small tidying up - this gives the following clients:",[1698,4022,1378],{"id":4023},"producer-2",[1317,4025,4027],{"className":2148,"code":4026,"language":1346,"meta":227,"style":227},"import java.time.Duration\nimport java.util.Properties\n\nimport org.apache.kafka.clients.producer.ProducerConfig.{BOOTSTRAP_SERVERS_CONFIG, CLIENT_ID_CONFIG, KEY_SERIALIZER_CLASS_CONFIG, VALUE_SERIALIZER_CLASS_CONFIG}\nimport org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}\nimport pureconfig.ConfigSource\nimport pureconfig.generic.auto._\n\ncase class Config(clientId: String,\n                  bootstrapServers: String,\n                  topic: String,\n                  serializer: String) {\n\n  def asProperties: Properties = {\n    val props = new Properties()\n\n    props.put(CLIENT_ID_CONFIG, clientId)\n    props.put(BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)\n    props.put(KEY_SERIALIZER_CLASS_CONFIG, serializer)\n    props.put(VALUE_SERIALIZER_CLASS_CONFIG, serializer)\n\n    props\n  }\n}\n\nobject ConfigProducer extends App {\n\n  ConfigSource.default.load[Config] match {\n    case Left(errors) =>\n      println(errors)\n      System.exit(1)\n\n    case Right(config: Config) =>\n      println(\"*** Starting Config Producer ***\")\n\n      val producer = new KafkaProducer[String, String](config.asProperties)\n\n      (1 to 5).foreach { i =>\n        producer.send(new ProducerRecord[String, String](config.topic, s\"key-$i\", s\"value-$i\"))\n      }\n\n      producer.close(Duration.ofMillis(100))\n\n      println(\"### Stopping Config Producer ###\")\n  }\n}\n",[1296,4028,4029,4043,4057,4061,4105,4137,4149,4169,4173,4191,4201,4211,4221,4225,4240,4255,4259,4269,4278,4287,4295,4299,4304,4308,4312,4316,4329,4333,4348,4359,4364,4377,4381,4399,4408,4412,4435,4439,4455,4498,4502,4506,4519,4523,4532,4537],{"__ignoreMap":227},[1397,4030,4031,4033,4035,4037,4039,4041],{"class":1399,"line":1400},[1397,4032,2329],{"class":1407},[1397,4034,2332],{"class":1414},[1397,4036,1133],{"class":1403},[1397,4038,2337],{"class":1414},[1397,4040,1133],{"class":1403},[1397,4042,2342],{"class":1414},[1397,4044,4045,4047,4049,4051,4053,4055],{"class":1399,"line":228},[1397,4046,2329],{"class":1407},[1397,4048,2332],{"class":1414},[1397,4050,1133],{"class":1403},[1397,4052,2353],{"class":1414},[1397,4054,1133],{"class":1403},[1397,4056,2358],{"class":1414},[1397,4058,4059],{"class":1399,"line":1426},[1397,4060,1423],{"emptyLinePlaceholder":234},[1397,4062,4063,4065,4067,4069,4071,4073,4075,4077,4079,4081,4083,4085,4087,4089,4091,4093,4095,4097,4099,4101,4103],{"class":1399,"line":1451},[1397,4064,2329],{"class":1407},[1397,4066,2369],{"class":1414},[1397,4068,1133],{"class":1403},[1397,4070,2374],{"class":1414},[1397,4072,1133],{"class":1403},[1397,4074,1344],{"class":1414},[1397,4076,1133],{"class":1403},[1397,4078,2383],{"class":1414},[1397,4080,1133],{"class":1403},[1397,4082,1377],{"class":1414},[1397,4084,1133],{"class":1403},[1397,4086,2392],{"class":1414},[1397,4088,2395],{"class":1403},[1397,4090,2398],{"class":1414},[1397,4092,1442],{"class":1403},[1397,4094,2403],{"class":1414},[1397,4096,1442],{"class":1403},[1397,4098,2408],{"class":1414},[1397,4100,1442],{"class":1403},[1397,4102,2413],{"class":1414},[1397,4104,1690],{"class":1403},[1397,4106,4107,4109,4111,4113,4115,4117,4119,4121,4123,4125,4127,4129,4131,4133,4135],{"class":1399,"line":1470},[1397,4108,2329],{"class":1407},[1397,4110,2369],{"class":1414},[1397,4112,1133],{"class":1403},[1397,4114,2374],{"class":1414},[1397,4116,1133],{"class":1403},[1397,4118,1344],{"class":1414},[1397,4120,1133],{"class":1403},[1397,4122,2383],{"class":1414},[1397,4124,1133],{"class":1403},[1397,4126,1377],{"class":1414},[1397,4128,2395],{"class":1403},[1397,4130,2442],{"class":1414},[1397,4132,1442],{"class":1403},[1397,4134,2447],{"class":1414},[1397,4136,1690],{"class":1403},[1397,4138,4139,4141,4144,4146],{"class":1399,"line":1489},[1397,4140,2329],{"class":1407},[1397,4142,4143],{"class":1414}," pureconfig",[1397,4145,1133],{"class":1403},[1397,4147,4148],{"class":1414},"ConfigSource\n",[1397,4150,4151,4153,4155,4157,4160,4162,4165,4167],{"class":1399,"line":1651},[1397,4152,2329],{"class":1407},[1397,4154,4143],{"class":1414},[1397,4156,1133],{"class":1403},[1397,4158,4159],{"class":1414},"generic",[1397,4161,1133],{"class":1403},[1397,4163,4164],{"class":1414},"auto",[1397,4166,1133],{"class":1403},[1397,4168,3021],{"class":1414},[1397,4170,4171],{"class":1399,"line":1675},[1397,4172,1423],{"emptyLinePlaceholder":234},[1397,4174,4175,4177,4179,4181,4183,4185,4187,4189],{"class":1399,"line":1687},[1397,4176,3743],{"class":1407},[1397,4178,3746],{"class":1407},[1397,4180,3749],{"class":1414},[1397,4182,1435],{"class":1403},[1397,4184,3754],{"class":2211},[1397,4186,2513],{"class":1403},[1397,4188,1520],{"class":1414},[1397,4190,2242],{"class":1403},[1397,4192,4193,4195,4197,4199],{"class":1399,"line":2275},[1397,4194,3765],{"class":2211},[1397,4196,2513],{"class":1403},[1397,4198,1520],{"class":1414},[1397,4200,2242],{"class":1403},[1397,4202,4203,4205,4207,4209],{"class":1399,"line":2281},[1397,4204,3776],{"class":2211},[1397,4206,2513],{"class":1403},[1397,4208,1520],{"class":1414},[1397,4210,2242],{"class":1403},[1397,4212,4213,4215,4217,4219],{"class":1399,"line":2540},[1397,4214,3787],{"class":2211},[1397,4216,2513],{"class":1403},[1397,4218,1520],{"class":1414},[1397,4220,1580],{"class":1403},[1397,4222,4223],{"class":1399,"line":2552},[1397,4224,1423],{"emptyLinePlaceholder":234},[1397,4226,4227,4229,4232,4234,4236,4238],{"class":1399,"line":2557},[1397,4228,2502],{"class":1407},[1397,4230,4231],{"class":1414}," asProperties",[1397,4233,1981],{"class":1407},[1397,4235,1415],{"class":1414},[1397,4237,2215],{"class":1407},[1397,4239,2493],{"class":1403},[1397,4241,4242,4244,4247,4249,4251,4253],{"class":1399,"line":2575},[1397,4243,2560],{"class":1407},[1397,4245,4246],{"class":2211}," props",[1397,4248,2215],{"class":1407},[1397,4250,1411],{"class":1407},[1397,4252,1415],{"class":1414},[1397,4254,2572],{"class":1403},[1397,4256,4257],{"class":1399,"line":2580},[1397,4258,1423],{"emptyLinePlaceholder":234},[1397,4260,4261,4264,4266],{"class":1399,"line":2594},[1397,4262,4263],{"class":1403},"    props.put(",[1397,4265,2403],{"class":1414},[1397,4267,4268],{"class":1403},", clientId)\n",[1397,4270,4271,4273,4275],{"class":1399,"line":2607},[1397,4272,4263],{"class":1403},[1397,4274,2398],{"class":1414},[1397,4276,4277],{"class":1403},", bootstrapServers)\n",[1397,4279,4280,4282,4284],{"class":1399,"line":2627},[1397,4281,4263],{"class":1403},[1397,4283,2408],{"class":1414},[1397,4285,4286],{"class":1403},", serializer)\n",[1397,4288,4289,4291,4293],{"class":1399,"line":2644},[1397,4290,4263],{"class":1403},[1397,4292,2413],{"class":1414},[1397,4294,4286],{"class":1403},[1397,4296,4297],{"class":1399,"line":2649},[1397,4298,1423],{"emptyLinePlaceholder":234},[1397,4300,4301],{"class":1399,"line":2675},[1397,4302,4303],{"class":1403},"    props\n",[1397,4305,4306],{"class":1399,"line":2680},[1397,4307,2855],{"class":1403},[1397,4309,4310],{"class":1399,"line":2693},[1397,4311,1690],{"class":1403},[1397,4313,4314],{"class":1399,"line":2698},[1397,4315,1423],{"emptyLinePlaceholder":234},[1397,4317,4318,4320,4323,4325,4327],{"class":1399,"line":2719},[1397,4319,2487],{"class":1407},[1397,4321,4322],{"class":1414}," ConfigProducer",[1397,4324,3645],{"class":1407},[1397,4326,3648],{"class":1414},[1397,4328,2493],{"class":1403},[1397,4330,4331],{"class":1399,"line":2737},[1397,4332,1423],{"emptyLinePlaceholder":234},[1397,4334,4335,4338,4340,4342,4344,4346],{"class":1399,"line":2753},[1397,4336,4337],{"class":1414},"  ConfigSource",[1397,4339,3945],{"class":1403},[1397,4341,3926],{"class":1414},[1397,4343,3950],{"class":1403},[1397,4345,3953],{"class":1407},[1397,4347,2493],{"class":1403},[1397,4349,4350,4352,4354,4356],{"class":1399,"line":2758},[1397,4351,3960],{"class":1407},[1397,4353,3963],{"class":1414},[1397,4355,3966],{"class":1403},[1397,4357,4358],{"class":1407},"=>\n",[1397,4360,4361],{"class":1399,"line":2778},[1397,4362,4363],{"class":1403},"      println(errors)\n",[1397,4365,4366,4369,4372,4375],{"class":1399,"line":2783},[1397,4367,4368],{"class":1414},"      System",[1397,4370,4371],{"class":1403},".exit(",[1397,4373,4374],{"class":1561},"1",[1397,4376,2549],{"class":1403},[1397,4378,4379],{"class":1399,"line":2806},[1397,4380,1423],{"emptyLinePlaceholder":234},[1397,4382,4383,4385,4387,4389,4391,4393,4395,4397],{"class":1399,"line":2811},[1397,4384,3960],{"class":1407},[1397,4386,3979],{"class":1414},[1397,4388,1435],{"class":1403},[1397,4390,3984],{"class":2211},[1397,4392,2513],{"class":1403},[1397,4394,3926],{"class":1414},[1397,4396,3991],{"class":1403},[1397,4398,4358],{"class":1407},[1397,4400,4401,4403,4406],{"class":1399,"line":2816},[1397,4402,2761],{"class":1403},[1397,4404,4405],{"class":1438},"\"*** Starting Config Producer ***\"",[1397,4407,2549],{"class":1403},[1397,4409,4410],{"class":1399,"line":2832},[1397,4411,1423],{"emptyLinePlaceholder":234},[1397,4413,4414,4416,4418,4420,4422,4424,4426,4428,4430,4432],{"class":1399,"line":2837},[1397,4415,2722],{"class":1407},[1397,4417,2654],{"class":2211},[1397,4419,2215],{"class":1407},[1397,4421,1411],{"class":1407},[1397,4423,2661],{"class":1414},[1397,4425,2519],{"class":1403},[1397,4427,1520],{"class":1414},[1397,4429,1442],{"class":1403},[1397,4431,1520],{"class":1414},[1397,4433,4434],{"class":1403},"](config.asProperties)\n",[1397,4436,4437],{"class":1399,"line":2847},[1397,4438,1423],{"emptyLinePlaceholder":234},[1397,4440,4441,4444,4446,4448,4450,4453],{"class":1399,"line":2852},[1397,4442,4443],{"class":1403},"      (",[1397,4445,4374],{"class":1561},[1397,4447,2711],{"class":1403},[1397,4449,2714],{"class":1561},[1397,4451,4452],{"class":1403},").foreach { i ",[1397,4454,4358],{"class":1407},[1397,4456,4457,4460,4462,4464,4466,4468,4470,4472,4475,4477,4480,4483,4485,4487,4489,4492,4494,4496],{"class":1399,"line":2858},[1397,4458,4459],{"class":1403},"        producer.send(",[1397,4461,2789],{"class":1407},[1397,4463,2792],{"class":1414},[1397,4465,2519],{"class":1403},[1397,4467,1520],{"class":1414},[1397,4469,1442],{"class":1403},[1397,4471,1520],{"class":1414},[1397,4473,4474],{"class":1403},"](config.topic, ",[1397,4476,2764],{"class":1407},[1397,4478,4479],{"class":1438},"\"key-",[1397,4481,4482],{"class":1403},"$i",[1397,4484,3420],{"class":1438},[1397,4486,1442],{"class":1403},[1397,4488,2764],{"class":1407},[1397,4490,4491],{"class":1438},"\"value-",[1397,4493,4482],{"class":1403},[1397,4495,3420],{"class":1438},[1397,4497,2224],{"class":1403},[1397,4499,4500],{"class":1399,"line":3435},[1397,4501,3432],{"class":1403},[1397,4503,4504],{"class":1399,"line":3446},[1397,4505,1423],{"emptyLinePlaceholder":234},[1397,4507,4508,4511,4513,4515,4517],{"class":1399,"line":3452},[1397,4509,4510],{"class":1403},"      producer.close(",[1397,4512,2822],{"class":1414},[1397,4514,2825],{"class":1403},[1397,4516,1956],{"class":1561},[1397,4518,2224],{"class":1403},[1397,4520,4521],{"class":1399,"line":3457},[1397,4522,1423],{"emptyLinePlaceholder":234},[1397,4524,4525,4527,4530],{"class":1399,"line":3462},[1397,4526,2761],{"class":1403},[1397,4528,4529],{"class":1438},"\"### Stopping Config Producer ###\"",[1397,4531,2549],{"class":1403},[1397,4533,4535],{"class":1399,"line":4534},45,[1397,4536,2855],{"class":1403},[1397,4538,4540],{"class":1399,"line":4539},46,[1397,4541,1690],{"class":1403},[1698,4543,1729],{"id":4544},"consumer-2",[1317,4546,4548],{"className":2148,"code":4547,"language":1346,"meta":227,"style":227},"import java.time.Duration\nimport java.util.Properties\n\nimport org.apache.kafka.clients.consumer.ConsumerConfig._\nimport org.apache.kafka.clients.consumer.KafkaConsumer\nimport pureconfig.ConfigSource\nimport pureconfig.generic.auto._\n\nimport scala.collection.JavaConverters._\n\ncase class Config(groupId: String,\n                  bootstrapServers: String,\n                  enableAutoCommit: String,\n                  autoCommitIntervalMs: String,\n                  autoOffsetReset: String,\n                  deserializer: String,\n                  topic: String\n                 ) {\n  def asProperties: Properties = {\n    val props = new Properties()\n\n    props.put(GROUP_ID_CONFIG, groupId)\n    props.put(BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)\n    props.put(ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit)\n    props.put(AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs)\n    props.put(AUTO_OFFSET_RESET_CONFIG, autoOffsetReset)\n    props.put(KEY_DESERIALIZER_CLASS_CONFIG, deserializer)\n    props.put(VALUE_DESERIALIZER_CLASS_CONFIG, deserializer)\n\n    props\n  }\n}\n\nobject ConfigConsumer extends App {\n  ConfigSource.default.load[Config] match {\n    case Left(errors) =>\n      println(errors)\n      System.exit(1)\n\n    case Right(config: Config) =>\n      println(\"*** Starting Config Consumer ***\")\n\n      val consumer = new KafkaConsumer[String, String](config.asProperties)\n\n      try {\n        consumer.subscribe(List(config.topic).asJava)\n\n        while (true) {\n          val records = consumer.poll(Duration.ofMillis(100)).asScala\n\n          for (record \u003C- records) {\n            println(s\"offset = ${record.offset}, key = ${record.key}, value = ${record.value}\")\n          }\n        }\n      } finally {\n        consumer.close()\n      }\n\n  }\n}\n",[1296,4549,4550,4564,4578,4582,4612,4638,4648,4666,4670,4690,4694,4712,4722,4732,4742,4752,4762,4770,4775,4789,4803,4807,4816,4824,4833,4842,4851,4860,4868,4872,4876,4880,4884,4888,4901,4915,4925,4929,4939,4943,4961,4970,4974,4996,5000,5007,5017,5022,5034,5055,5060,5072,5096,5102,5107,5117,5123,5128,5133,5138],{"__ignoreMap":227},[1397,4551,4552,4554,4556,4558,4560,4562],{"class":1399,"line":1400},[1397,4553,2329],{"class":1407},[1397,4555,2332],{"class":1414},[1397,4557,1133],{"class":1403},[1397,4559,2337],{"class":1414},[1397,4561,1133],{"class":1403},[1397,4563,2342],{"class":1414},[1397,4565,4566,4568,4570,4572,4574,4576],{"class":1399,"line":228},[1397,4567,2329],{"class":1407},[1397,4569,2332],{"class":1414},[1397,4571,1133],{"class":1403},[1397,4573,2353],{"class":1414},[1397,4575,1133],{"class":1403},[1397,4577,2358],{"class":1414},[1397,4579,4580],{"class":1399,"line":1426},[1397,4581,1423],{"emptyLinePlaceholder":234},[1397,4583,4584,4586,4588,4590,4592,4594,4596,4598,4600,4602,4604,4606,4608,4610],{"class":1399,"line":1451},[1397,4585,2329],{"class":1407},[1397,4587,2369],{"class":1414},[1397,4589,1133],{"class":1403},[1397,4591,2374],{"class":1414},[1397,4593,1133],{"class":1403},[1397,4595,1344],{"class":1414},[1397,4597,1133],{"class":1403},[1397,4599,2383],{"class":1414},[1397,4601,1133],{"class":1403},[1397,4603,1728],{"class":1414},[1397,4605,1133],{"class":1403},[1397,4607,3016],{"class":1414},[1397,4609,1133],{"class":1403},[1397,4611,3021],{"class":1414},[1397,4613,4614,4616,4618,4620,4622,4624,4626,4628,4630,4632,4634,4636],{"class":1399,"line":1470},[1397,4615,2329],{"class":1407},[1397,4617,2369],{"class":1414},[1397,4619,1133],{"class":1403},[1397,4621,2374],{"class":1414},[1397,4623,1133],{"class":1403},[1397,4625,1344],{"class":1414},[1397,4627,1133],{"class":1403},[1397,4629,2383],{"class":1414},[1397,4631,1133],{"class":1403},[1397,4633,1728],{"class":1414},[1397,4635,1133],{"class":1403},[1397,4637,3048],{"class":1414},[1397,4639,4640,4642,4644,4646],{"class":1399,"line":1489},[1397,4641,2329],{"class":1407},[1397,4643,4143],{"class":1414},[1397,4645,1133],{"class":1403},[1397,4647,4148],{"class":1414},[1397,4649,4650,4652,4654,4656,4658,4660,4662,4664],{"class":1399,"line":1651},[1397,4651,2329],{"class":1407},[1397,4653,4143],{"class":1414},[1397,4655,1133],{"class":1403},[1397,4657,4159],{"class":1414},[1397,4659,1133],{"class":1403},[1397,4661,4164],{"class":1414},[1397,4663,1133],{"class":1403},[1397,4665,3021],{"class":1414},[1397,4667,4668],{"class":1399,"line":1675},[1397,4669,1423],{"emptyLinePlaceholder":234},[1397,4671,4672,4674,4677,4679,4682,4684,4686,4688],{"class":1399,"line":1687},[1397,4673,2329],{"class":1407},[1397,4675,4676],{"class":1414}," scala",[1397,4678,1133],{"class":1403},[1397,4680,4681],{"class":1414},"collection",[1397,4683,1133],{"class":1403},[1397,4685,3091],{"class":1414},[1397,4687,1133],{"class":1403},[1397,4689,3021],{"class":1414},[1397,4691,4692],{"class":1399,"line":2275},[1397,4693,1423],{"emptyLinePlaceholder":234},[1397,4695,4696,4698,4700,4702,4704,4706,4708,4710],{"class":1399,"line":2281},[1397,4697,3743],{"class":1407},[1397,4699,3746],{"class":1407},[1397,4701,3749],{"class":1414},[1397,4703,1435],{"class":1403},[1397,4705,3814],{"class":2211},[1397,4707,2513],{"class":1403},[1397,4709,1520],{"class":1414},[1397,4711,2242],{"class":1403},[1397,4713,4714,4716,4718,4720],{"class":1399,"line":2540},[1397,4715,3765],{"class":2211},[1397,4717,2513],{"class":1403},[1397,4719,1520],{"class":1414},[1397,4721,2242],{"class":1403},[1397,4723,4724,4726,4728,4730],{"class":1399,"line":2552},[1397,4725,3835],{"class":2211},[1397,4727,2513],{"class":1403},[1397,4729,1520],{"class":1414},[1397,4731,2242],{"class":1403},[1397,4733,4734,4736,4738,4740],{"class":1399,"line":2557},[1397,4735,3846],{"class":2211},[1397,4737,2513],{"class":1403},[1397,4739,1520],{"class":1414},[1397,4741,2242],{"class":1403},[1397,4743,4744,4746,4748,4750],{"class":1399,"line":2575},[1397,4745,3857],{"class":2211},[1397,4747,2513],{"class":1403},[1397,4749,1520],{"class":1414},[1397,4751,2242],{"class":1403},[1397,4753,4754,4756,4758,4760],{"class":1399,"line":2580},[1397,4755,3868],{"class":2211},[1397,4757,2513],{"class":1403},[1397,4759,1520],{"class":1414},[1397,4761,2242],{"class":1403},[1397,4763,4764,4766,4768],{"class":1399,"line":2594},[1397,4765,3776],{"class":2211},[1397,4767,2513],{"class":1403},[1397,4769,3883],{"class":1414},[1397,4771,4772],{"class":1399,"line":2607},[1397,4773,4774],{"class":1403},"                 ) {\n",[1397,4776,4777,4779,4781,4783,4785,4787],{"class":1399,"line":2627},[1397,4778,2502],{"class":1407},[1397,4780,4231],{"class":1414},[1397,4782,1981],{"class":1407},[1397,4784,1415],{"class":1414},[1397,4786,2215],{"class":1407},[1397,4788,2493],{"class":1403},[1397,4790,4791,4793,4795,4797,4799,4801],{"class":1399,"line":2644},[1397,4792,2560],{"class":1407},[1397,4794,4246],{"class":2211},[1397,4796,2215],{"class":1407},[1397,4798,1411],{"class":1407},[1397,4800,1415],{"class":1414},[1397,4802,2572],{"class":1403},[1397,4804,4805],{"class":1399,"line":2649},[1397,4806,1423],{"emptyLinePlaceholder":234},[1397,4808,4809,4811,4813],{"class":1399,"line":2675},[1397,4810,4263],{"class":1403},[1397,4812,3182],{"class":1414},[1397,4814,4815],{"class":1403},", groupId)\n",[1397,4817,4818,4820,4822],{"class":1399,"line":2680},[1397,4819,4263],{"class":1403},[1397,4821,2398],{"class":1414},[1397,4823,4277],{"class":1403},[1397,4825,4826,4828,4830],{"class":1399,"line":2693},[1397,4827,4263],{"class":1403},[1397,4829,3207],{"class":1414},[1397,4831,4832],{"class":1403},", enableAutoCommit)\n",[1397,4834,4835,4837,4839],{"class":1399,"line":2698},[1397,4836,4263],{"class":1403},[1397,4838,3220],{"class":1414},[1397,4840,4841],{"class":1403},", autoCommitIntervalMs)\n",[1397,4843,4844,4846,4848],{"class":1399,"line":2719},[1397,4845,4263],{"class":1403},[1397,4847,3233],{"class":1414},[1397,4849,4850],{"class":1403},", autoOffsetReset)\n",[1397,4852,4853,4855,4857],{"class":1399,"line":2737},[1397,4854,4263],{"class":1403},[1397,4856,3246],{"class":1414},[1397,4858,4859],{"class":1403},", deserializer)\n",[1397,4861,4862,4864,4866],{"class":1399,"line":2753},[1397,4863,4263],{"class":1403},[1397,4865,3265],{"class":1414},[1397,4867,4859],{"class":1403},[1397,4869,4870],{"class":1399,"line":2758},[1397,4871,1423],{"emptyLinePlaceholder":234},[1397,4873,4874],{"class":1399,"line":2778},[1397,4875,4303],{"class":1403},[1397,4877,4878],{"class":1399,"line":2783},[1397,4879,2855],{"class":1403},[1397,4881,4882],{"class":1399,"line":2806},[1397,4883,1690],{"class":1403},[1397,4885,4886],{"class":1399,"line":2811},[1397,4887,1423],{"emptyLinePlaceholder":234},[1397,4889,4890,4892,4895,4897,4899],{"class":1399,"line":2816},[1397,4891,2487],{"class":1407},[1397,4893,4894],{"class":1414}," ConfigConsumer",[1397,4896,3645],{"class":1407},[1397,4898,3648],{"class":1414},[1397,4900,2493],{"class":1403},[1397,4902,4903,4905,4907,4909,4911,4913],{"class":1399,"line":2832},[1397,4904,4337],{"class":1414},[1397,4906,3945],{"class":1403},[1397,4908,3926],{"class":1414},[1397,4910,3950],{"class":1403},[1397,4912,3953],{"class":1407},[1397,4914,2493],{"class":1403},[1397,4916,4917,4919,4921,4923],{"class":1399,"line":2837},[1397,4918,3960],{"class":1407},[1397,4920,3963],{"class":1414},[1397,4922,3966],{"class":1403},[1397,4924,4358],{"class":1407},[1397,4926,4927],{"class":1399,"line":2847},[1397,4928,4363],{"class":1403},[1397,4930,4931,4933,4935,4937],{"class":1399,"line":2852},[1397,4932,4368],{"class":1414},[1397,4934,4371],{"class":1403},[1397,4936,4374],{"class":1561},[1397,4938,2549],{"class":1403},[1397,4940,4941],{"class":1399,"line":2858},[1397,4942,1423],{"emptyLinePlaceholder":234},[1397,4944,4945,4947,4949,4951,4953,4955,4957,4959],{"class":1399,"line":3435},[1397,4946,3960],{"class":1407},[1397,4948,3979],{"class":1414},[1397,4950,1435],{"class":1403},[1397,4952,3984],{"class":2211},[1397,4954,2513],{"class":1403},[1397,4956,3926],{"class":1414},[1397,4958,3991],{"class":1403},[1397,4960,4358],{"class":1407},[1397,4962,4963,4965,4968],{"class":1399,"line":3446},[1397,4964,2761],{"class":1403},[1397,4966,4967],{"class":1438},"\"*** Starting Config Consumer ***\"",[1397,4969,2549],{"class":1403},[1397,4971,4972],{"class":1399,"line":3452},[1397,4973,1423],{"emptyLinePlaceholder":234},[1397,4975,4976,4978,4980,4982,4984,4986,4988,4990,4992,4994],{"class":1399,"line":3457},[1397,4977,2722],{"class":1407},[1397,4979,3286],{"class":2211},[1397,4981,2215],{"class":1407},[1397,4983,1411],{"class":1407},[1397,4985,3293],{"class":1414},[1397,4987,2519],{"class":1403},[1397,4989,1520],{"class":1414},[1397,4991,1442],{"class":1403},[1397,4993,1520],{"class":1414},[1397,4995,4434],{"class":1403},[1397,4997,4998],{"class":1399,"line":3462},[1397,4999,1423],{"emptyLinePlaceholder":234},[1397,5001,5002,5005],{"class":1399,"line":4534},[1397,5003,5004],{"class":1407},"      try",[1397,5006,2493],{"class":1403},[1397,5008,5009,5012,5014],{"class":1399,"line":4539},[1397,5010,5011],{"class":1403},"        consumer.subscribe(",[1397,5013,3336],{"class":1414},[1397,5015,5016],{"class":1403},"(config.topic).asJava)\n",[1397,5018,5020],{"class":1399,"line":5019},47,[1397,5021,1423],{"emptyLinePlaceholder":234},[1397,5023,5025,5028,5030,5032],{"class":1399,"line":5024},48,[1397,5026,5027],{"class":1407},"        while",[1397,5029,1550],{"class":1403},[1397,5031,1921],{"class":1561},[1397,5033,1580],{"class":1403},[1397,5035,5037,5040,5042,5044,5046,5048,5050,5052],{"class":1399,"line":5036},49,[1397,5038,5039],{"class":1407},"          val",[1397,5041,3362],{"class":2211},[1397,5043,2215],{"class":1407},[1397,5045,3367],{"class":1403},[1397,5047,2822],{"class":1414},[1397,5049,2825],{"class":1403},[1397,5051,1956],{"class":1561},[1397,5053,5054],{"class":1403},")).asScala\n",[1397,5056,5058],{"class":1399,"line":5057},50,[1397,5059,1423],{"emptyLinePlaceholder":234},[1397,5061,5063,5066,5068,5070],{"class":1399,"line":5062},51,[1397,5064,5065],{"class":1407},"          for",[1397,5067,3387],{"class":1403},[1397,5069,2706],{"class":1407},[1397,5071,1984],{"class":1403},[1397,5073,5075,5078,5080,5082,5084,5086,5088,5090,5092,5094],{"class":1399,"line":5074},52,[1397,5076,5077],{"class":1403},"            println(",[1397,5079,2764],{"class":1407},[1397,5081,3402],{"class":1438},[1397,5083,3405],{"class":1403},[1397,5085,3408],{"class":1438},[1397,5087,3411],{"class":1403},[1397,5089,3414],{"class":1438},[1397,5091,3417],{"class":1403},[1397,5093,3420],{"class":1438},[1397,5095,2549],{"class":1403},[1397,5097,5099],{"class":1399,"line":5098},53,[1397,5100,5101],{"class":1403},"          }\n",[1397,5103,5105],{"class":1399,"line":5104},54,[1397,5106,3427],{"class":1403},[1397,5108,5110,5113,5115],{"class":1399,"line":5109},55,[1397,5111,5112],{"class":1403},"      } ",[1397,5114,3441],{"class":1407},[1397,5116,2493],{"class":1403},[1397,5118,5120],{"class":1399,"line":5119},56,[1397,5121,5122],{"class":1403},"        consumer.close()\n",[1397,5124,5126],{"class":1399,"line":5125},57,[1397,5127,3432],{"class":1403},[1397,5129,5131],{"class":1399,"line":5130},58,[1397,5132,1423],{"emptyLinePlaceholder":234},[1397,5134,5136],{"class":1399,"line":5135},59,[1397,5137,2855],{"class":1403},[1397,5139,5141],{"class":1399,"line":5140},60,[1397,5142,1690],{"class":1403},[1698,5144,5146],{"id":5145},"build-and-run","Build and Run",[211,5148,5149],{},"For each client - we can check it compiles and run it using sbt as before:",[1317,5151,5152],{"className":2869,"code":2870,"language":2871,"meta":227,"style":227},[1296,5153,5154],{"__ignoreMap":227},[1397,5155,5156,5158],{"class":1399,"line":1400},[1397,5157,2878],{"class":1414},[1397,5159,2881],{"class":1438},[211,5161,5162],{},"sbt's run command will also find classes that extend App so we can also still run:",[1317,5164,5165],{"className":2869,"code":2887,"language":2871,"meta":227,"style":227},[1296,5166,5167],{"__ignoreMap":227},[1397,5168,5169,5171],{"class":1399,"line":1400},[1397,5170,2878],{"class":1414},[1397,5172,2896],{"class":1438},[211,5174,5175],{},"Producer output:",[1317,5177,5180],{"className":5178,"code":5179,"language":1322},[1320],"*** Starting Config Producer ***\n### Stopping Config Producer ###\n",[1296,5181,5179],{"__ignoreMap":227},[211,5183,5184],{},"Consumer output:",[1317,5186,5189],{"className":5187,"code":5188,"language":1322},[1320],"*** Starting Config Consumer ***\noffset = 0, key = key-1, value = value-1\noffset = 1, key = key-2, value = value-2\noffset = 2, key = key-3, value = value-3\noffset = 3, key = key-4, value = value-4\noffset = 4, key = key-5, value = value-5\n",[1296,5190,5188],{"__ignoreMap":227},[1254,5192,2061],{"id":2060},[211,5194,5195],{},"In this step we tidied up the producer and consumer a little and moved our configuration out to a config file.",[1254,5197,3515],{"id":3514},[444,5199,5200,5206],{},[447,5201,5202],{},[1118,5203,3524],{"href":5204,"rel":5205,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fscala-v2-config\u002Fconfigproducer",[1122,1123,1124],[447,5207,5208],{},[1118,5209,3531],{"href":5210,"rel":5211,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fscala-v2-config\u002Fconfigconsumer",[1122,1123,1124],[2066,5213,5214],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":227,"searchDepth":228,"depth":228,"links":5216},[5217,5218,5225,5230,5231],{"id":3567,"depth":228,"text":3568},{"id":3662,"depth":228,"text":3663,"children":5219},[5220,5221,5222,5223,5224],{"id":3678,"depth":1426,"text":3679},{"id":3696,"depth":1426,"text":3697},{"id":3726,"depth":1426,"text":3727},{"id":3891,"depth":1426,"text":3892},{"id":4002,"depth":1426,"text":4003},{"id":4016,"depth":228,"text":4017,"children":5226},[5227,5228,5229],{"id":4023,"depth":1426,"text":1378},{"id":4544,"depth":1426,"text":1729},{"id":5145,"depth":1426,"text":5146},{"id":2060,"depth":228,"text":2061},{"id":3514,"depth":228,"text":3515},"2019-05-03 11:33 +0200",{"updated":1337},"\u002F2019\u002F05\u002F03\u002Fkafka-java-to-scala-scala-v2",{"title":3557,"description":1355},{"loc":5234},"2019\u002F05\u002F03\u002Fkafka-java-to-scala-scala-v2",[1344,1346,1377,1728,5239],"pureconfig","pebskUlIafAvqsrJQHdIFAdJywmZ4gTFJI2Up0IxDns",{"id":5242,"title":5243,"body":5244,"category":27,"date":6694,"description":1355,"embedImage":231,"extension":232,"image":231,"intro":6695,"meta":6696,"navigation":234,"path":6697,"seo":6698,"series":1340,"sitemap":6699,"stem":6700,"tags":6701,"__hash__":6704},"content\u002F2019\u002F05\u002F08\u002Fkafka-java-to-scala-akka-streams-basics.md","Kafka - java to scala - akka streams basics",{"type":208,"value":5245,"toc":6682},[5246,5248,5255,5262,5266,5275,5284,5287,5291,5298,5309,5313,5322,5326,5332,5375,5378,5393,5404,5419,5422,5425,5582,5585,5761,5764,6088,6092,6095,6098,6136,6139,6194,6196,6404,6408,6411,6466,6470,6474,6514,6518,6550,6554,6580,6584,6652,6654,6665,6668,6670,6679],[211,5247,1355],{},[211,5249,5250,5251,5254],{},"In the ",[1118,5252,5253],{"href":1279},"previous post"," we updated our clients to use a configuration library and to make them somewhat more scala-like.",[211,5256,5257,5258,5261],{},"Moving forward - we will look at using them together with akka streams but before we can do that - we'll take a ",[1250,5259,5260],{},"very"," quick trip on akka streams in general.",[1254,5263,5265],{"id":5264},"brief-intro-to-akka-streams","Brief intro to akka streams",[211,5267,5268,5269,5274],{},"Akka itself has a large amount of information available on the ",[1118,5270,5273],{"href":5271,"rel":5272,"target":1125},"https:\u002F\u002Fdoc.akka.io",[1122,1123,1124],"doc.akka.io"," site. This goes into much more detail and covers a much larger area than this post will - so do give it a read if you want more info.",[211,5276,5277,5278,5283],{},"I also found ",[1118,5279,5282],{"href":5280,"rel":5281,"target":1125},"https:\u002F\u002Fscalac.io\u002Fstreams-in-akka-scala-introduction\u002F",[1122,1123,1124],"this article at scalac.io"," to be very informative.",[211,5285,5286],{},"Akka streams allows us to use the akka actor system to handle streaming data.",[1254,5288,5290],{"id":5289},"terminology","Terminology",[211,5292,5293,5294,1981],{},"This is very clearly explained on the article at ",[1118,5295,5297],{"href":5280,"rel":5296,"target":1125},[1122,1123,1124],"scalac.io",[444,5299,5300,5303,5306],{},[447,5301,5302],{},"Source - a source of data - one output",[447,5304,5305],{},"Flow - a transformation of data - one input, one output",[447,5307,5308],{},"Sink - a consumer of data - one input.",[1254,5310,5312],{"id":5311},"examples","Examples",[211,5314,5315,5316,5321],{},"Let's look briefly at two examples (both taken from the ",[1118,5317,5320],{"href":5318,"rel":5319,"target":1125},"https:\u002F\u002Fdoc.akka.io\u002Fdocs\u002Fakka\u002Fcurrent\u002Fstream\u002Fstream-quickstart.html",[1122,1123,1124],"akka streams quickstart guide",")",[1254,5323,5325],{"id":5324},"akka-stream-example-1-just-an-int-stream","Akka stream example 1 - just an int stream",[211,5327,5328,5329,1133],{},"For our source - we'll just use a range of integers. Our source will only use the main output type so we'll mark it as ",[1296,5330,5331],{},"Int, NotUsed",[1317,5333,5335],{"className":2148,"code":5334,"language":1346,"meta":227,"style":227},"val source: Source[Int, NotUsed] = Source(1 to 100)\n",[1296,5336,5337],{"__ignoreMap":227},[1397,5338,5339,5341,5344,5346,5349,5351,5354,5356,5359,5361,5363,5365,5367,5369,5371,5373],{"class":1399,"line":1400},[1397,5340,3912],{"class":1407},[1397,5342,5343],{"class":2211}," source",[1397,5345,1981],{"class":1407},[1397,5347,5348],{"class":1414}," Source",[1397,5350,2519],{"class":1403},[1397,5352,5353],{"class":1414},"Int",[1397,5355,1442],{"class":1403},[1397,5357,5358],{"class":1414},"NotUsed",[1397,5360,3950],{"class":1403},[1397,5362,1408],{"class":1407},[1397,5364,5348],{"class":1414},[1397,5366,1435],{"class":1403},[1397,5368,4374],{"class":1561},[1397,5370,2711],{"class":1403},[1397,5372,1956],{"class":1561},[1397,5374,2549],{"class":1403},[211,5376,5377],{},"Now we need to consume the source - let's just print each int.",[1317,5379,5381],{"className":2148,"code":5380,"language":1346,"meta":227,"style":227},"source.runForeach(i => println(i))\n",[1296,5382,5383],{"__ignoreMap":227},[1397,5384,5385,5388,5390],{"class":1399,"line":1400},[1397,5386,5387],{"class":1403},"source.runForeach(i ",[1397,5389,3969],{"class":1407},[1397,5391,5392],{"class":1403}," println(i))\n",[211,5394,5395,5396,5399,5400,5403],{},"Lastly - since this source has a finite amount of data - we can get the result of ",[1296,5397,5398],{},"runForEach"," which is of type ",[1296,5401,5402],{},"Future[Done]"," and hook up termination of the Actor system when the stream is completely consumed:",[1317,5405,5407],{"className":2148,"code":5406,"language":1346,"meta":227,"style":227}," done.onComplete(_ => system.terminate())\n",[1296,5408,5409],{"__ignoreMap":227},[1397,5410,5411,5414,5416],{"class":1399,"line":1400},[1397,5412,5413],{"class":1403}," done.onComplete(_ ",[1397,5415,3969],{"class":1407},[1397,5417,5418],{"class":1403}," system.terminate())\n",[211,5420,5421],{},"This code does expect a number of things to be implicitly available, the actor system, the materializer, and an execution context.",[211,5423,5424],{},"This gives the following object:",[1317,5426,5428],{"className":2148,"code":5427,"language":1346,"meta":227,"style":227},"object IntSeqExample extends App {\n    implicit val system = ActorSystem(\"IntSeqExample\")\n    implicit val materializer = ActorMaterializer()\n    implicit val ec = system.dispatcher\n\n    val source: Source[Int, NotUsed] = Source(1 to 100)\n\n    val done: Future[Done] = source.runForeach(i => println(i))\n\n    done.onComplete(_ => system.terminate())\n}\n",[1296,5429,5430,5443,5465,5481,5495,5499,5533,5537,5565,5569,5578],{"__ignoreMap":227},[1397,5431,5432,5434,5437,5439,5441],{"class":1399,"line":1400},[1397,5433,2487],{"class":1407},[1397,5435,5436],{"class":1414}," IntSeqExample",[1397,5438,3645],{"class":1407},[1397,5440,3648],{"class":1414},[1397,5442,2493],{"class":1403},[1397,5444,5445,5448,5450,5453,5455,5458,5460,5463],{"class":1399,"line":228},[1397,5446,5447],{"class":1407},"    implicit",[1397,5449,2208],{"class":1407},[1397,5451,5452],{"class":2211}," system",[1397,5454,2215],{"class":1407},[1397,5456,5457],{"class":1414}," ActorSystem",[1397,5459,1435],{"class":1403},[1397,5461,5462],{"class":1438},"\"IntSeqExample\"",[1397,5464,2549],{"class":1403},[1397,5466,5467,5469,5471,5474,5476,5479],{"class":1399,"line":1426},[1397,5468,5447],{"class":1407},[1397,5470,2208],{"class":1407},[1397,5472,5473],{"class":2211}," materializer",[1397,5475,2215],{"class":1407},[1397,5477,5478],{"class":1414}," ActorMaterializer",[1397,5480,2572],{"class":1403},[1397,5482,5483,5485,5487,5490,5492],{"class":1399,"line":1451},[1397,5484,5447],{"class":1407},[1397,5486,2208],{"class":1407},[1397,5488,5489],{"class":2211}," ec",[1397,5491,2215],{"class":1407},[1397,5493,5494],{"class":1403}," system.dispatcher\n",[1397,5496,5497],{"class":1399,"line":1470},[1397,5498,1423],{"emptyLinePlaceholder":234},[1397,5500,5501,5503,5505,5507,5509,5511,5513,5515,5517,5519,5521,5523,5525,5527,5529,5531],{"class":1399,"line":1489},[1397,5502,2560],{"class":1407},[1397,5504,5343],{"class":2211},[1397,5506,1981],{"class":1407},[1397,5508,5348],{"class":1414},[1397,5510,2519],{"class":1403},[1397,5512,5353],{"class":1414},[1397,5514,1442],{"class":1403},[1397,5516,5358],{"class":1414},[1397,5518,3950],{"class":1403},[1397,5520,1408],{"class":1407},[1397,5522,5348],{"class":1414},[1397,5524,1435],{"class":1403},[1397,5526,4374],{"class":1561},[1397,5528,2711],{"class":1403},[1397,5530,1956],{"class":1561},[1397,5532,2549],{"class":1403},[1397,5534,5535],{"class":1399,"line":1651},[1397,5536,1423],{"emptyLinePlaceholder":234},[1397,5538,5539,5541,5544,5546,5549,5551,5554,5556,5558,5561,5563],{"class":1399,"line":1675},[1397,5540,2560],{"class":1407},[1397,5542,5543],{"class":2211}," done",[1397,5545,1981],{"class":1407},[1397,5547,5548],{"class":1414}," Future",[1397,5550,2519],{"class":1403},[1397,5552,5553],{"class":1414},"Done",[1397,5555,3950],{"class":1403},[1397,5557,1408],{"class":1407},[1397,5559,5560],{"class":1403}," source.runForeach(i ",[1397,5562,3969],{"class":1407},[1397,5564,5392],{"class":1403},[1397,5566,5567],{"class":1399,"line":1687},[1397,5568,1423],{"emptyLinePlaceholder":234},[1397,5570,5571,5574,5576],{"class":1399,"line":2275},[1397,5572,5573],{"class":1403},"    done.onComplete(_ ",[1397,5575,3969],{"class":1407},[1397,5577,5418],{"class":1403},[1397,5579,5580],{"class":1399,"line":2281},[1397,5581,1690],{"class":1403},[211,5583,5584],{},"However - this code uses some nice shortcut methods that hide some of what is going on. Let's break it down so that we can see the source and the sink here.",[1317,5586,5588],{"className":2148,"code":5587,"language":1346,"meta":227,"style":227},"object IntSeqExample2 extends App {\n    implicit val system = ActorSystem(\"IntSeqExample2\")\n    implicit val materializer = ActorMaterializer()\n    implicit val ec = system.dispatcher\n\n    val source: Source[Int, NotUsed] = Source(1 to 100)\n\n    val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)\n\n    val done = source.runWith(sink)\n\n    done.onComplete(_ => system.terminate())\n}\n",[1296,5589,5590,5603,5622,5636,5648,5652,5686,5690,5730,5734,5745,5749,5757],{"__ignoreMap":227},[1397,5591,5592,5594,5597,5599,5601],{"class":1399,"line":1400},[1397,5593,2487],{"class":1407},[1397,5595,5596],{"class":1414}," IntSeqExample2",[1397,5598,3645],{"class":1407},[1397,5600,3648],{"class":1414},[1397,5602,2493],{"class":1403},[1397,5604,5605,5607,5609,5611,5613,5615,5617,5620],{"class":1399,"line":228},[1397,5606,5447],{"class":1407},[1397,5608,2208],{"class":1407},[1397,5610,5452],{"class":2211},[1397,5612,2215],{"class":1407},[1397,5614,5457],{"class":1414},[1397,5616,1435],{"class":1403},[1397,5618,5619],{"class":1438},"\"IntSeqExample2\"",[1397,5621,2549],{"class":1403},[1397,5623,5624,5626,5628,5630,5632,5634],{"class":1399,"line":1426},[1397,5625,5447],{"class":1407},[1397,5627,2208],{"class":1407},[1397,5629,5473],{"class":2211},[1397,5631,2215],{"class":1407},[1397,5633,5478],{"class":1414},[1397,5635,2572],{"class":1403},[1397,5637,5638,5640,5642,5644,5646],{"class":1399,"line":1451},[1397,5639,5447],{"class":1407},[1397,5641,2208],{"class":1407},[1397,5643,5489],{"class":2211},[1397,5645,2215],{"class":1407},[1397,5647,5494],{"class":1403},[1397,5649,5650],{"class":1399,"line":1470},[1397,5651,1423],{"emptyLinePlaceholder":234},[1397,5653,5654,5656,5658,5660,5662,5664,5666,5668,5670,5672,5674,5676,5678,5680,5682,5684],{"class":1399,"line":1489},[1397,5655,2560],{"class":1407},[1397,5657,5343],{"class":2211},[1397,5659,1981],{"class":1407},[1397,5661,5348],{"class":1414},[1397,5663,2519],{"class":1403},[1397,5665,5353],{"class":1414},[1397,5667,1442],{"class":1403},[1397,5669,5358],{"class":1414},[1397,5671,3950],{"class":1403},[1397,5673,1408],{"class":1407},[1397,5675,5348],{"class":1414},[1397,5677,1435],{"class":1403},[1397,5679,4374],{"class":1561},[1397,5681,2711],{"class":1403},[1397,5683,1956],{"class":1561},[1397,5685,2549],{"class":1403},[1397,5687,5688],{"class":1399,"line":1651},[1397,5689,1423],{"emptyLinePlaceholder":234},[1397,5691,5692,5694,5697,5699,5702,5704,5706,5708,5711,5713,5715,5718,5720,5722,5725,5727],{"class":1399,"line":1675},[1397,5693,2560],{"class":1407},[1397,5695,5696],{"class":2211}," sink",[1397,5698,1981],{"class":1407},[1397,5700,5701],{"class":1414}," Sink",[1397,5703,2519],{"class":1403},[1397,5705,5353],{"class":1414},[1397,5707,1442],{"class":1403},[1397,5709,5710],{"class":1414},"Future",[1397,5712,2519],{"class":1403},[1397,5714,5553],{"class":1414},[1397,5716,5717],{"class":1403},"]] ",[1397,5719,1408],{"class":1407},[1397,5721,5701],{"class":1414},[1397,5723,5724],{"class":1403},".foreach[",[1397,5726,5353],{"class":1414},[1397,5728,5729],{"class":1403},"](println)\n",[1397,5731,5732],{"class":1399,"line":1687},[1397,5733,1423],{"emptyLinePlaceholder":234},[1397,5735,5736,5738,5740,5742],{"class":1399,"line":2275},[1397,5737,2560],{"class":1407},[1397,5739,5543],{"class":2211},[1397,5741,2215],{"class":1407},[1397,5743,5744],{"class":1403}," source.runWith(sink)\n",[1397,5746,5747],{"class":1399,"line":2281},[1397,5748,1423],{"emptyLinePlaceholder":234},[1397,5750,5751,5753,5755],{"class":1399,"line":2540},[1397,5752,5573],{"class":1403},[1397,5754,3969],{"class":1407},[1397,5756,5418],{"class":1403},[1397,5758,5759],{"class":1399,"line":2552},[1397,5760,1690],{"class":1403},[211,5762,5763],{},"We can add some flows too:",[1317,5765,5767],{"className":2148,"code":5766,"language":1346,"meta":227,"style":227},"object IntSeqExample3 extends App {\n  implicit val system = ActorSystem(\"IntSeqExample2\")\n  implicit val materializer = ActorMaterializer()\n\n  val evenFlow: Flow[Int, Int, NotUsed] = Flow[Int].filter(i => i % 2 == 0)\n  val toStringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(i => i.toString)\n\n  val source: Source[Int, NotUsed] = Source(1 to 100)\n  val evenSource: Source[Int, NotUsed] = source.via(evenFlow)\n  val evenStringSource: Source[String, NotUsed] = evenSource.via(toStringFlow)\n\n  val sink: Sink[String, Future[Done]] = Sink.foreach[String](println)\n\n  val done = evenStringSource.runWith(sink)\n\n  implicit val ec = system.dispatcher\n  done.onComplete(_ => system.terminate())\n}\n",[1296,5768,5769,5782,5801,5815,5819,5875,5916,5920,5954,5980,6006,6010,6044,6048,6059,6063,6075,6084],{"__ignoreMap":227},[1397,5770,5771,5773,5776,5778,5780],{"class":1399,"line":1400},[1397,5772,2487],{"class":1407},[1397,5774,5775],{"class":1414}," IntSeqExample3",[1397,5777,3645],{"class":1407},[1397,5779,3648],{"class":1414},[1397,5781,2493],{"class":1403},[1397,5783,5784,5787,5789,5791,5793,5795,5797,5799],{"class":1399,"line":228},[1397,5785,5786],{"class":1407},"  implicit",[1397,5788,2208],{"class":1407},[1397,5790,5452],{"class":2211},[1397,5792,2215],{"class":1407},[1397,5794,5457],{"class":1414},[1397,5796,1435],{"class":1403},[1397,5798,5619],{"class":1438},[1397,5800,2549],{"class":1403},[1397,5802,5803,5805,5807,5809,5811,5813],{"class":1399,"line":1426},[1397,5804,5786],{"class":1407},[1397,5806,2208],{"class":1407},[1397,5808,5473],{"class":2211},[1397,5810,2215],{"class":1407},[1397,5812,5478],{"class":1414},[1397,5814,2572],{"class":1403},[1397,5816,5817],{"class":1399,"line":1451},[1397,5818,1423],{"emptyLinePlaceholder":234},[1397,5820,5821,5824,5827,5829,5832,5834,5836,5838,5840,5842,5844,5846,5848,5850,5852,5854,5857,5859,5861,5864,5867,5870,5873],{"class":1399,"line":1470},[1397,5822,5823],{"class":1407},"  val",[1397,5825,5826],{"class":2211}," evenFlow",[1397,5828,1981],{"class":1407},[1397,5830,5831],{"class":1414}," Flow",[1397,5833,2519],{"class":1403},[1397,5835,5353],{"class":1414},[1397,5837,1442],{"class":1403},[1397,5839,5353],{"class":1414},[1397,5841,1442],{"class":1403},[1397,5843,5358],{"class":1414},[1397,5845,3950],{"class":1403},[1397,5847,1408],{"class":1407},[1397,5849,5831],{"class":1414},[1397,5851,2519],{"class":1403},[1397,5853,5353],{"class":1414},[1397,5855,5856],{"class":1403},"].filter(i ",[1397,5858,3969],{"class":1407},[1397,5860,1556],{"class":1403},[1397,5862,5863],{"class":1407},"%",[1397,5865,5866],{"class":1561}," 2",[1397,5868,5869],{"class":1407}," ==",[1397,5871,5872],{"class":1561}," 0",[1397,5874,2549],{"class":1403},[1397,5876,5877,5879,5882,5884,5886,5888,5890,5892,5894,5896,5898,5900,5902,5904,5906,5908,5911,5913],{"class":1399,"line":1489},[1397,5878,5823],{"class":1407},[1397,5880,5881],{"class":2211}," toStringFlow",[1397,5883,1981],{"class":1407},[1397,5885,5831],{"class":1414},[1397,5887,2519],{"class":1403},[1397,5889,5353],{"class":1414},[1397,5891,1442],{"class":1403},[1397,5893,1520],{"class":1414},[1397,5895,1442],{"class":1403},[1397,5897,5358],{"class":1414},[1397,5899,3950],{"class":1403},[1397,5901,1408],{"class":1407},[1397,5903,5831],{"class":1414},[1397,5905,2519],{"class":1403},[1397,5907,5353],{"class":1414},[1397,5909,5910],{"class":1403},"].map(i ",[1397,5912,3969],{"class":1407},[1397,5914,5915],{"class":1403}," i.toString)\n",[1397,5917,5918],{"class":1399,"line":1651},[1397,5919,1423],{"emptyLinePlaceholder":234},[1397,5921,5922,5924,5926,5928,5930,5932,5934,5936,5938,5940,5942,5944,5946,5948,5950,5952],{"class":1399,"line":1675},[1397,5923,5823],{"class":1407},[1397,5925,5343],{"class":2211},[1397,5927,1981],{"class":1407},[1397,5929,5348],{"class":1414},[1397,5931,2519],{"class":1403},[1397,5933,5353],{"class":1414},[1397,5935,1442],{"class":1403},[1397,5937,5358],{"class":1414},[1397,5939,3950],{"class":1403},[1397,5941,1408],{"class":1407},[1397,5943,5348],{"class":1414},[1397,5945,1435],{"class":1403},[1397,5947,4374],{"class":1561},[1397,5949,2711],{"class":1403},[1397,5951,1956],{"class":1561},[1397,5953,2549],{"class":1403},[1397,5955,5956,5958,5961,5963,5965,5967,5969,5971,5973,5975,5977],{"class":1399,"line":1687},[1397,5957,5823],{"class":1407},[1397,5959,5960],{"class":2211}," evenSource",[1397,5962,1981],{"class":1407},[1397,5964,5348],{"class":1414},[1397,5966,2519],{"class":1403},[1397,5968,5353],{"class":1414},[1397,5970,1442],{"class":1403},[1397,5972,5358],{"class":1414},[1397,5974,3950],{"class":1403},[1397,5976,1408],{"class":1407},[1397,5978,5979],{"class":1403}," source.via(evenFlow)\n",[1397,5981,5982,5984,5987,5989,5991,5993,5995,5997,5999,6001,6003],{"class":1399,"line":2275},[1397,5983,5823],{"class":1407},[1397,5985,5986],{"class":2211}," evenStringSource",[1397,5988,1981],{"class":1407},[1397,5990,5348],{"class":1414},[1397,5992,2519],{"class":1403},[1397,5994,1520],{"class":1414},[1397,5996,1442],{"class":1403},[1397,5998,5358],{"class":1414},[1397,6000,3950],{"class":1403},[1397,6002,1408],{"class":1407},[1397,6004,6005],{"class":1403}," evenSource.via(toStringFlow)\n",[1397,6007,6008],{"class":1399,"line":2281},[1397,6009,1423],{"emptyLinePlaceholder":234},[1397,6011,6012,6014,6016,6018,6020,6022,6024,6026,6028,6030,6032,6034,6036,6038,6040,6042],{"class":1399,"line":2540},[1397,6013,5823],{"class":1407},[1397,6015,5696],{"class":2211},[1397,6017,1981],{"class":1407},[1397,6019,5701],{"class":1414},[1397,6021,2519],{"class":1403},[1397,6023,1520],{"class":1414},[1397,6025,1442],{"class":1403},[1397,6027,5710],{"class":1414},[1397,6029,2519],{"class":1403},[1397,6031,5553],{"class":1414},[1397,6033,5717],{"class":1403},[1397,6035,1408],{"class":1407},[1397,6037,5701],{"class":1414},[1397,6039,5724],{"class":1403},[1397,6041,1520],{"class":1414},[1397,6043,5729],{"class":1403},[1397,6045,6046],{"class":1399,"line":2552},[1397,6047,1423],{"emptyLinePlaceholder":234},[1397,6049,6050,6052,6054,6056],{"class":1399,"line":2557},[1397,6051,5823],{"class":1407},[1397,6053,5543],{"class":2211},[1397,6055,2215],{"class":1407},[1397,6057,6058],{"class":1403}," evenStringSource.runWith(sink)\n",[1397,6060,6061],{"class":1399,"line":2575},[1397,6062,1423],{"emptyLinePlaceholder":234},[1397,6064,6065,6067,6069,6071,6073],{"class":1399,"line":2580},[1397,6066,5786],{"class":1407},[1397,6068,2208],{"class":1407},[1397,6070,5489],{"class":2211},[1397,6072,2215],{"class":1407},[1397,6074,5494],{"class":1403},[1397,6076,6077,6080,6082],{"class":1399,"line":2594},[1397,6078,6079],{"class":1403},"  done.onComplete(_ ",[1397,6081,3969],{"class":1407},[1397,6083,5418],{"class":1403},[1397,6085,6086],{"class":1399,"line":2607},[1397,6087,1690],{"class":1403},[1254,6089,6091],{"id":6090},"akka-stream-example-2-factorials","Akka stream example 2 - factorials",[211,6093,6094],{},"This example starts with the exact same source (integer range).",[211,6096,6097],{},"It then operates over the stream - calculating an accumulated value:",[1317,6099,6101],{"className":2148,"code":6100,"language":1346,"meta":227,"style":227},"val factorials = source.scan(BigInt(1))((acc, next) => acc * next)\n",[1296,6102,6103],{"__ignoreMap":227},[1397,6104,6105,6107,6110,6112,6115,6118,6120,6122,6125,6127,6130,6133],{"class":1399,"line":1400},[1397,6106,3912],{"class":1407},[1397,6108,6109],{"class":2211}," factorials",[1397,6111,2215],{"class":1407},[1397,6113,6114],{"class":1403}," source.scan(",[1397,6116,6117],{"class":1414},"BigInt",[1397,6119,1435],{"class":1403},[1397,6121,4374],{"class":1561},[1397,6123,6124],{"class":1403},"))((acc, next) ",[1397,6126,3969],{"class":1407},[1397,6128,6129],{"class":1403}," acc ",[1397,6131,6132],{"class":1407},"*",[1397,6134,6135],{"class":1403}," next)\n",[211,6137,6138],{},"And then - we can consume it by zipping it together with a second source - to provide the list of factorials:",[1317,6140,6142],{"className":2148,"code":6141,"language":1346,"meta":227,"style":227},"factorials\n      .zipWith(Source(0 to 100))((num, idx) => s\"$idx! = $num\")\n      .runForeach(println)\n",[1296,6143,6144,6149,6189],{"__ignoreMap":227},[1397,6145,6146],{"class":1399,"line":1400},[1397,6147,6148],{"class":1403},"factorials\n",[1397,6150,6151,6154,6157,6159,6162,6164,6166,6169,6171,6174,6176,6179,6182,6185,6187],{"class":1399,"line":228},[1397,6152,6153],{"class":1403},"      .zipWith(",[1397,6155,6156],{"class":1414},"Source",[1397,6158,1435],{"class":1403},[1397,6160,6161],{"class":1561},"0",[1397,6163,2711],{"class":1403},[1397,6165,1956],{"class":1561},[1397,6167,6168],{"class":1403},"))((num, idx) ",[1397,6170,3969],{"class":1407},[1397,6172,6173],{"class":1407}," s",[1397,6175,3420],{"class":1438},[1397,6177,6178],{"class":1403},"$idx",[1397,6180,6181],{"class":1438},"! = ",[1397,6183,6184],{"class":1403},"$num",[1397,6186,3420],{"class":1438},[1397,6188,2549],{"class":1403},[1397,6190,6191],{"class":1399,"line":1426},[1397,6192,6193],{"class":1403},"      .runForeach(println)\n",[211,6195,5424],{},[1317,6197,6199],{"className":2148,"code":6198,"language":1346,"meta":227,"style":227},"object FactorialExample extends App {\n    implicit val system = ActorSystem(\"FactorialExample\")\n    implicit val materializer = ActorMaterializer()\n    implicit val ec = system.dispatcher\n\n    val source: Source[Int, NotUsed] = Source(1 to 100)\n\n    val factorials = source.scan(BigInt(1))((acc, next) => acc * next)\n\n    val done: Future[Done] = factorials\n      .zipWith(Source(0 to 100))((num, idx) => s\"$idx! = $num\")\n      .runForeach(println)\n\n    done.onComplete(_ => system.terminate())\n}\n",[1296,6200,6201,6214,6233,6247,6259,6263,6297,6301,6327,6331,6352,6384,6388,6392,6400],{"__ignoreMap":227},[1397,6202,6203,6205,6208,6210,6212],{"class":1399,"line":1400},[1397,6204,2487],{"class":1407},[1397,6206,6207],{"class":1414}," FactorialExample",[1397,6209,3645],{"class":1407},[1397,6211,3648],{"class":1414},[1397,6213,2493],{"class":1403},[1397,6215,6216,6218,6220,6222,6224,6226,6228,6231],{"class":1399,"line":228},[1397,6217,5447],{"class":1407},[1397,6219,2208],{"class":1407},[1397,6221,5452],{"class":2211},[1397,6223,2215],{"class":1407},[1397,6225,5457],{"class":1414},[1397,6227,1435],{"class":1403},[1397,6229,6230],{"class":1438},"\"FactorialExample\"",[1397,6232,2549],{"class":1403},[1397,6234,6235,6237,6239,6241,6243,6245],{"class":1399,"line":1426},[1397,6236,5447],{"class":1407},[1397,6238,2208],{"class":1407},[1397,6240,5473],{"class":2211},[1397,6242,2215],{"class":1407},[1397,6244,5478],{"class":1414},[1397,6246,2572],{"class":1403},[1397,6248,6249,6251,6253,6255,6257],{"class":1399,"line":1451},[1397,6250,5447],{"class":1407},[1397,6252,2208],{"class":1407},[1397,6254,5489],{"class":2211},[1397,6256,2215],{"class":1407},[1397,6258,5494],{"class":1403},[1397,6260,6261],{"class":1399,"line":1470},[1397,6262,1423],{"emptyLinePlaceholder":234},[1397,6264,6265,6267,6269,6271,6273,6275,6277,6279,6281,6283,6285,6287,6289,6291,6293,6295],{"class":1399,"line":1489},[1397,6266,2560],{"class":1407},[1397,6268,5343],{"class":2211},[1397,6270,1981],{"class":1407},[1397,6272,5348],{"class":1414},[1397,6274,2519],{"class":1403},[1397,6276,5353],{"class":1414},[1397,6278,1442],{"class":1403},[1397,6280,5358],{"class":1414},[1397,6282,3950],{"class":1403},[1397,6284,1408],{"class":1407},[1397,6286,5348],{"class":1414},[1397,6288,1435],{"class":1403},[1397,6290,4374],{"class":1561},[1397,6292,2711],{"class":1403},[1397,6294,1956],{"class":1561},[1397,6296,2549],{"class":1403},[1397,6298,6299],{"class":1399,"line":1651},[1397,6300,1423],{"emptyLinePlaceholder":234},[1397,6302,6303,6305,6307,6309,6311,6313,6315,6317,6319,6321,6323,6325],{"class":1399,"line":1675},[1397,6304,2560],{"class":1407},[1397,6306,6109],{"class":2211},[1397,6308,2215],{"class":1407},[1397,6310,6114],{"class":1403},[1397,6312,6117],{"class":1414},[1397,6314,1435],{"class":1403},[1397,6316,4374],{"class":1561},[1397,6318,6124],{"class":1403},[1397,6320,3969],{"class":1407},[1397,6322,6129],{"class":1403},[1397,6324,6132],{"class":1407},[1397,6326,6135],{"class":1403},[1397,6328,6329],{"class":1399,"line":1687},[1397,6330,1423],{"emptyLinePlaceholder":234},[1397,6332,6333,6335,6337,6339,6341,6343,6345,6347,6349],{"class":1399,"line":2275},[1397,6334,2560],{"class":1407},[1397,6336,5543],{"class":2211},[1397,6338,1981],{"class":1407},[1397,6340,5548],{"class":1414},[1397,6342,2519],{"class":1403},[1397,6344,5553],{"class":1414},[1397,6346,3950],{"class":1403},[1397,6348,1408],{"class":1407},[1397,6350,6351],{"class":1403}," factorials\n",[1397,6353,6354,6356,6358,6360,6362,6364,6366,6368,6370,6372,6374,6376,6378,6380,6382],{"class":1399,"line":2281},[1397,6355,6153],{"class":1403},[1397,6357,6156],{"class":1414},[1397,6359,1435],{"class":1403},[1397,6361,6161],{"class":1561},[1397,6363,2711],{"class":1403},[1397,6365,1956],{"class":1561},[1397,6367,6168],{"class":1403},[1397,6369,3969],{"class":1407},[1397,6371,6173],{"class":1407},[1397,6373,3420],{"class":1438},[1397,6375,6178],{"class":1403},[1397,6377,6181],{"class":1438},[1397,6379,6184],{"class":1403},[1397,6381,3420],{"class":1438},[1397,6383,2549],{"class":1403},[1397,6385,6386],{"class":1399,"line":2540},[1397,6387,6193],{"class":1403},[1397,6389,6390],{"class":1399,"line":2552},[1397,6391,1423],{"emptyLinePlaceholder":234},[1397,6393,6394,6396,6398],{"class":1399,"line":2557},[1397,6395,5573],{"class":1403},[1397,6397,3969],{"class":1407},[1397,6399,5418],{"class":1403},[1397,6401,6402],{"class":1399,"line":2575},[1397,6403,1690],{"class":1403},[1254,6405,6407],{"id":6406},"compile-and-run","Compile and run",[211,6409,6410],{},"Start the sbt console, clean, compile and run each client:",[1317,6412,6414],{"className":2869,"code":6413,"language":2871,"meta":227,"style":227},"$ sbt\n> clean\n> compile\n> runMain example.IntSeqExample\n> runMain example.IntSeqExample2\n> runMain example.IntSeqExample3\n> runMain example.FactorialExample\n",[1296,6415,6416,6424,6432,6438,6445,6452,6459],{"__ignoreMap":227},[1397,6417,6418,6421],{"class":1399,"line":1400},[1397,6419,6420],{"class":1414},"$",[1397,6422,6423],{"class":1438}," sbt\n",[1397,6425,6426,6429],{"class":1399,"line":228},[1397,6427,6428],{"class":1407},">",[1397,6430,6431],{"class":1403}," clean\n",[1397,6433,6434,6436],{"class":1399,"line":1426},[1397,6435,6428],{"class":1407},[1397,6437,2881],{"class":1403},[1397,6439,6440,6442],{"class":1399,"line":1451},[1397,6441,6428],{"class":1407},[1397,6443,6444],{"class":1403}," runMain example.IntSeqExample\n",[1397,6446,6447,6449],{"class":1399,"line":1470},[1397,6448,6428],{"class":1407},[1397,6450,6451],{"class":1403}," runMain example.IntSeqExample2\n",[1397,6453,6454,6456],{"class":1399,"line":1489},[1397,6455,6428],{"class":1407},[1397,6457,6458],{"class":1403}," runMain example.IntSeqExample3\n",[1397,6460,6461,6463],{"class":1399,"line":1651},[1397,6462,6428],{"class":1407},[1397,6464,6465],{"class":1403}," runMain example.FactorialExample\n",[1698,6467,6469],{"id":6468},"output","Output",[2120,6471,6473],{"id":6472},"integer-sequence","Integer sequence",[1317,6475,6477],{"className":2869,"code":6476,"language":2871,"meta":227,"style":227},"1\n2\n3\n...\n98\n99\n100\n",[1296,6478,6479,6484,6489,6494,6499,6504,6509],{"__ignoreMap":227},[1397,6480,6481],{"class":1399,"line":1400},[1397,6482,6483],{"class":1414},"1\n",[1397,6485,6486],{"class":1399,"line":228},[1397,6487,6488],{"class":1414},"2\n",[1397,6490,6491],{"class":1399,"line":1426},[1397,6492,6493],{"class":1414},"3\n",[1397,6495,6496],{"class":1399,"line":1451},[1397,6497,6498],{"class":1561},"...\n",[1397,6500,6501],{"class":1399,"line":1470},[1397,6502,6503],{"class":1414},"98\n",[1397,6505,6506],{"class":1399,"line":1489},[1397,6507,6508],{"class":1414},"99\n",[1397,6510,6511],{"class":1399,"line":1651},[1397,6512,6513],{"class":1414},"100\n",[2120,6515,6517],{"id":6516},"integer-sequence-2","Integer sequence 2",[1317,6519,6520],{"className":2869,"code":6476,"language":2871,"meta":227,"style":227},[1296,6521,6522,6526,6530,6534,6538,6542,6546],{"__ignoreMap":227},[1397,6523,6524],{"class":1399,"line":1400},[1397,6525,6483],{"class":1414},[1397,6527,6528],{"class":1399,"line":228},[1397,6529,6488],{"class":1414},[1397,6531,6532],{"class":1399,"line":1426},[1397,6533,6493],{"class":1414},[1397,6535,6536],{"class":1399,"line":1451},[1397,6537,6498],{"class":1561},[1397,6539,6540],{"class":1399,"line":1470},[1397,6541,6503],{"class":1414},[1397,6543,6544],{"class":1399,"line":1489},[1397,6545,6508],{"class":1414},[1397,6547,6548],{"class":1399,"line":1651},[1397,6549,6513],{"class":1414},[2120,6551,6553],{"id":6552},"integer-sequence-3","Integer sequence 3",[1317,6555,6557],{"className":2869,"code":6556,"language":2871,"meta":227,"style":227},"2\n4\n...\n98\n100\n",[1296,6558,6559,6563,6568,6572,6576],{"__ignoreMap":227},[1397,6560,6561],{"class":1399,"line":1400},[1397,6562,6488],{"class":1414},[1397,6564,6565],{"class":1399,"line":228},[1397,6566,6567],{"class":1414},"4\n",[1397,6569,6570],{"class":1399,"line":1426},[1397,6571,6498],{"class":1561},[1397,6573,6574],{"class":1399,"line":1451},[1397,6575,6503],{"class":1414},[1397,6577,6578],{"class":1399,"line":1470},[1397,6579,6513],{"class":1414},[2120,6581,6583],{"id":6582},"factorial","Factorial",[1317,6585,6587],{"className":2869,"code":6586,"language":2871,"meta":227,"style":227},"0! = 1\n1! = 1\n2! = 2\n...\n98! = 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000\n99! = 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000\n100! = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000\n",[1296,6588,6589,6599,6608,6618,6622,6632,6642],{"__ignoreMap":227},[1397,6590,6591,6594,6596],{"class":1399,"line":1400},[1397,6592,6593],{"class":1414},"0!",[1397,6595,2215],{"class":1438},[1397,6597,6598],{"class":1561}," 1\n",[1397,6600,6601,6604,6606],{"class":1399,"line":228},[1397,6602,6603],{"class":1414},"1!",[1397,6605,2215],{"class":1438},[1397,6607,6598],{"class":1561},[1397,6609,6610,6613,6615],{"class":1399,"line":1426},[1397,6611,6612],{"class":1414},"2!",[1397,6614,2215],{"class":1438},[1397,6616,6617],{"class":1561}," 2\n",[1397,6619,6620],{"class":1399,"line":1451},[1397,6621,6498],{"class":1561},[1397,6623,6624,6627,6629],{"class":1399,"line":1470},[1397,6625,6626],{"class":1414},"98!",[1397,6628,2215],{"class":1438},[1397,6630,6631],{"class":1561}," 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000\n",[1397,6633,6634,6637,6639],{"class":1399,"line":1489},[1397,6635,6636],{"class":1414},"99!",[1397,6638,2215],{"class":1438},[1397,6640,6641],{"class":1561}," 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000\n",[1397,6643,6644,6647,6649],{"class":1399,"line":1651},[1397,6645,6646],{"class":1414},"100!",[1397,6648,2215],{"class":1438},[1397,6650,6651],{"class":1561}," 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000\n",[1254,6653,2061],{"id":2060},[211,6655,6656,6657,6661,6662,1133],{},"This was an extremely brief look at akka-streams - taken mostly from the ",[1118,6658,6660],{"href":5318,"rel":6659,"target":1125},[1122,1123,1124],"akka streams quick start guide"," and from ",[1118,6663,5297],{"href":5280,"rel":6664,"target":1125},[1122,1123,1124],[211,6666,6667],{},"Our next step will be to use akka streams for our producer and consumer clients.",[1254,6669,3515],{"id":3514},[444,6671,6672],{},[447,6673,6674],{},[1118,6675,6678],{"href":6676,"rel":6677,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fakka-streams-basics",[1122,1123,1124],"Streams project",[2066,6680,6681],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":227,"searchDepth":228,"depth":228,"links":6683},[6684,6685,6686,6687,6688,6689,6692,6693],{"id":5264,"depth":228,"text":5265},{"id":5289,"depth":228,"text":5290},{"id":5311,"depth":228,"text":5312},{"id":5324,"depth":228,"text":5325},{"id":6090,"depth":228,"text":6091},{"id":6406,"depth":228,"text":6407,"children":6690},[6691],{"id":6468,"depth":1426,"text":6469},{"id":2060,"depth":228,"text":2061},{"id":3514,"depth":228,"text":3515},"2019-05-08 12:41 +0200","In the previous post we updated our clients to use a configuration library and to make them somewhat more scala-like. Moving forward - we will look at using them together with akka streams but before we can do that - we'll take a very quick trip on akka streams in general.",{"updated":1337},"\u002F2019\u002F05\u002F08\u002Fkafka-java-to-scala-akka-streams-basics",{"title":5243,"description":1355},{"loc":6697},"2019\u002F05\u002F08\u002Fkafka-java-to-scala-akka-streams-basics",[1346,6702,6703],"akka","akka streams","FAyXFo2hjs7qBqGLYjKG-idFBoSRe1sQnKgBeaSo6-I",{"id":6706,"title":6707,"body":6708,"category":27,"date":7705,"description":1355,"embedImage":231,"extension":232,"image":231,"intro":7706,"meta":7707,"navigation":234,"path":7708,"seo":7709,"series":1340,"sitemap":7710,"stem":7711,"tags":7712,"__hash__":7713},"content\u002F2019\u002F05\u002F15\u002Fkafka-java-to-scala-akka-streams-kafka.md","Kafka - java to scala - akka streams kafka",{"type":208,"value":6709,"toc":7692},[6710,6712,6717,6720,6726,6731,6759,6762,6765,6767,6769,6775,6777,6783,6785,6788,6790,6803,6806,6920,6923,6943,7153,7155,7158,7161,7317,7320,7331,7487,7489,7492,7502,7505,7579,7582,7662,7664,7667,7670,7673,7675,7689],[211,6711,1355],{},[211,6713,5250,6714,6716],{},[1118,6715,5253],{"href":1286}," we took a look at akka streams in general.",[211,6718,6719],{},"Let's apply that to our producer and consumer.",[211,6721,6722,6723,1133],{},"We'll start with the same project setup as we used in ",[1118,6724,6725],{"href":1279},"the configuration project",[211,6727,6728,6729,1981],{},"There are two changes to ",[1296,6730,2123],{},[444,6732,6733,6747],{},[447,6734,6735,6736,2711,6739,6742,6743,6746],{},"Set the ",[1296,6737,6738],{},"name",[1296,6740,6741],{},"AkkaProducer","\u002F",[1296,6744,6745],{},"AkkaConsumer"," as appropriate",[447,6748,6749,6750,6755,6756],{},"Add the ",[1118,6751,6754],{"href":6752,"rel":6753,"target":1125},"https:\u002F\u002Fdoc.akka.io\u002Fdocs\u002Falpakka-kafka\u002Fcurrent\u002Fhome.html",[1122,1123,1124],"Alpakka Kafka"," dependency ",[1296,6757,6758],{},"\"com.typesafe.akka\" %% \"akka-stream-kafka\" % \"2.0.0\"",[211,6760,6761],{},"The project directory (build.properties) are the same as before.",[211,6763,6764],{},"The src\u002Fmain\u002Fresources\u002Fapplication.conf files are very similar. We change the client\u002Fgroup IDs, the topic and we remove the serializer\u002Fdeserializer. The reason for this is that changing the typing of the messages would also require a lot of changes in code to match so doesn't really need to be a configurable.",[1254,6766,3926],{"id":3984},[1698,6768,1378],{"id":1377},[1317,6770,6773],{"className":6771,"code":6772,"language":1322},[1320],"bootstrap-servers = \"localhost:29092\"\ntopic = \"akka-streams-topic\"\n",[1296,6774,6772],{"__ignoreMap":227},[1698,6776,1729],{"id":1728},[1317,6778,6781],{"className":6779,"code":6780,"language":1322},[1320],"client-id = \"akka-streams-consumer\"\ngroup-id = \"akka-streams-consumer\"\nbootstrap-servers = \"localhost:29092\"\ntopic = \"akka-streams-topic\"\nenable-auto-commit = \"true\"\nauto-commit-interval-ms = \"1000\"\nauto-offset-reset = \"earliest\"\n",[1296,6782,6780],{"__ignoreMap":227},[1254,6784,1297],{"id":1296},[211,6786,6787],{},"OK - so how does the code look now?",[1698,6789,1378],{"id":3733},[211,6791,6792,6793,6797,6798,6802],{},"We still have a config case class and we still load the config with pureconfig (note - the kafka libraries for akka-streams can read application.conf themselves if you format it for them - see ",[1118,6794,1377],{"href":6795,"rel":6796,"target":1125},"https:\u002F\u002Fdoc.akka.io\u002Fdocs\u002Falpakka-kafka\u002Fcurrent\u002Fproducer.html#settings",[1122,1123,1124]," and ",[1118,6799,1728],{"href":6800,"rel":6801,"target":1125},"https:\u002F\u002Fdoc.akka.io\u002Fdocs\u002Falpakka-kafka\u002Fcurrent\u002Fconsumer.html#settings",[1122,1123,1124]," documentation).",[211,6804,6805],{},"However - once we have a configuration - we want to create the producer settings. For the producer that's simple - it takes a system and serializers and then we set the bootstrap server. We have no other options we want to set here - but we could add them if we did have (we'll see this in the consumer shortly).",[1317,6807,6809],{"className":2148,"code":6808,"language":1346,"meta":227,"style":227},"  private def buildProducerSettings(sys: ActorSystem, config: Config) = {\n    val keySerializer = Serdes.String().serializer()\n    val valueSerializer = Serdes.Integer().serializer().asInstanceOf[Serializer[Int]]\n\n    ProducerSettings(sys, keySerializer, valueSerializer)\n      .withBootstrapServers(config.bootstrapServers)\n  }\n",[1296,6810,6811,6846,6865,6899,6903,6911,6916],{"__ignoreMap":227},[1397,6812,6813,6816,6819,6822,6824,6827,6829,6832,6834,6836,6838,6840,6842,6844],{"class":1399,"line":1400},[1397,6814,6815],{"class":1407},"  private",[1397,6817,6818],{"class":1407}," def",[1397,6820,6821],{"class":1414}," buildProducerSettings",[1397,6823,1435],{"class":1403},[1397,6825,6826],{"class":2211},"sys",[1397,6828,2513],{"class":1403},[1397,6830,6831],{"class":1414},"ActorSystem",[1397,6833,1442],{"class":1403},[1397,6835,3984],{"class":2211},[1397,6837,2513],{"class":1403},[1397,6839,3926],{"class":1414},[1397,6841,3991],{"class":1403},[1397,6843,1408],{"class":1407},[1397,6845,2493],{"class":1403},[1397,6847,6848,6850,6853,6855,6858,6860,6862],{"class":1399,"line":228},[1397,6849,2560],{"class":1407},[1397,6851,6852],{"class":2211}," keySerializer",[1397,6854,2215],{"class":1407},[1397,6856,6857],{"class":1414}," Serdes",[1397,6859,1133],{"class":1403},[1397,6861,1520],{"class":1414},[1397,6863,6864],{"class":1403},"().serializer()\n",[1397,6866,6867,6869,6872,6874,6876,6878,6881,6884,6887,6889,6892,6894,6896],{"class":1399,"line":1426},[1397,6868,2560],{"class":1407},[1397,6870,6871],{"class":2211}," valueSerializer",[1397,6873,2215],{"class":1407},[1397,6875,6857],{"class":1414},[1397,6877,1133],{"class":1403},[1397,6879,6880],{"class":1414},"Integer",[1397,6882,6883],{"class":1403},"().serializer().",[1397,6885,6886],{"class":1561},"asInstanceOf",[1397,6888,2519],{"class":1403},[1397,6890,6891],{"class":1414},"Serializer",[1397,6893,2519],{"class":1403},[1397,6895,5353],{"class":1414},[1397,6897,6898],{"class":1403},"]]\n",[1397,6900,6901],{"class":1399,"line":1451},[1397,6902,1423],{"emptyLinePlaceholder":234},[1397,6904,6905,6908],{"class":1399,"line":1470},[1397,6906,6907],{"class":1414},"    ProducerSettings",[1397,6909,6910],{"class":1403},"(sys, keySerializer, valueSerializer)\n",[1397,6912,6913],{"class":1399,"line":1489},[1397,6914,6915],{"class":1403},"      .withBootstrapServers(config.bootstrapServers)\n",[1397,6917,6918],{"class":1399,"line":1651},[1397,6919,2855],{"class":1403},[211,6921,6922],{},"Finally we set up our akka stream:",[444,6924,6925,6928,6931,6937,6940],{},[447,6926,6927],{},"Source: an akka streams source that is the sequence of integers from 0 to 10000",[447,6929,6930],{},"Flow: doubles the value - just for fun",[447,6932,6933,6934],{},"Flow: creates a producer message with a record of ",[1397,6935,6936],{},"String, Int",[447,6938,6939],{},"Flow: send the message to the producer which sends it to kafka",[447,6941,6942],{},"Sink: consume each response from kafka and print what was done",[1317,6944,6946],{"className":2148,"code":6945,"language":1346,"meta":227,"style":227},"      println(\"*** Starting Producer ***\")\n\n      implicit val sys = ActorSystem()\n      implicit val mat = ActorMaterializer()\n\n      val producerSettings: ProducerSettings[String, Int] = buildProducerSettings(sys, config)\n\n      Source\n        .fromIterator(() => (0 to 10000).toIterator)\n        .map(i => i * 2)\n        .map { i =>\n          ProducerMessage.Message(new ProducerRecord[String, Int](config.topic, i), i)\n        }\n        .via(Producer.flexiFlow(producerSettings))\n        .runWith {\n          Sink.foreach(res => println(s\"Wrote ${res.passThrough} to ${config.topic}\"))\n        }\n",[1296,6947,6948,6957,6961,6977,6992,6996,7023,7027,7032,7051,7066,7073,7100,7104,7114,7119,7149],{"__ignoreMap":227},[1397,6949,6950,6952,6955],{"class":1399,"line":1400},[1397,6951,2761],{"class":1403},[1397,6953,6954],{"class":1438},"\"*** Starting Producer ***\"",[1397,6956,2549],{"class":1403},[1397,6958,6959],{"class":1399,"line":228},[1397,6960,1423],{"emptyLinePlaceholder":234},[1397,6962,6963,6966,6968,6971,6973,6975],{"class":1399,"line":1426},[1397,6964,6965],{"class":1407},"      implicit",[1397,6967,2208],{"class":1407},[1397,6969,6970],{"class":2211}," sys",[1397,6972,2215],{"class":1407},[1397,6974,5457],{"class":1414},[1397,6976,2572],{"class":1403},[1397,6978,6979,6981,6983,6986,6988,6990],{"class":1399,"line":1451},[1397,6980,6965],{"class":1407},[1397,6982,2208],{"class":1407},[1397,6984,6985],{"class":2211}," mat",[1397,6987,2215],{"class":1407},[1397,6989,5478],{"class":1414},[1397,6991,2572],{"class":1403},[1397,6993,6994],{"class":1399,"line":1470},[1397,6995,1423],{"emptyLinePlaceholder":234},[1397,6997,6998,7000,7003,7005,7008,7010,7012,7014,7016,7018,7020],{"class":1399,"line":1489},[1397,6999,2722],{"class":1407},[1397,7001,7002],{"class":2211}," producerSettings",[1397,7004,1981],{"class":1407},[1397,7006,7007],{"class":1414}," ProducerSettings",[1397,7009,2519],{"class":1403},[1397,7011,1520],{"class":1414},[1397,7013,1442],{"class":1403},[1397,7015,5353],{"class":1414},[1397,7017,3950],{"class":1403},[1397,7019,1408],{"class":1407},[1397,7021,7022],{"class":1403}," buildProducerSettings(sys, config)\n",[1397,7024,7025],{"class":1399,"line":1651},[1397,7026,1423],{"emptyLinePlaceholder":234},[1397,7028,7029],{"class":1399,"line":1675},[1397,7030,7031],{"class":1414},"      Source\n",[1397,7033,7034,7037,7039,7041,7043,7045,7048],{"class":1399,"line":1687},[1397,7035,7036],{"class":1403},"        .fromIterator(() ",[1397,7038,3969],{"class":1407},[1397,7040,1550],{"class":1403},[1397,7042,6161],{"class":1561},[1397,7044,2711],{"class":1403},[1397,7046,7047],{"class":1561},"10000",[1397,7049,7050],{"class":1403},").toIterator)\n",[1397,7052,7053,7056,7058,7060,7062,7064],{"class":1399,"line":2275},[1397,7054,7055],{"class":1403},"        .map(i ",[1397,7057,3969],{"class":1407},[1397,7059,1556],{"class":1403},[1397,7061,6132],{"class":1407},[1397,7063,5866],{"class":1561},[1397,7065,2549],{"class":1403},[1397,7067,7068,7071],{"class":1399,"line":2281},[1397,7069,7070],{"class":1403},"        .map { i ",[1397,7072,4358],{"class":1407},[1397,7074,7075,7078,7080,7083,7085,7087,7089,7091,7093,7095,7097],{"class":1399,"line":2540},[1397,7076,7077],{"class":1414},"          ProducerMessage",[1397,7079,1133],{"class":1403},[1397,7081,7082],{"class":1414},"Message",[1397,7084,1435],{"class":1403},[1397,7086,2789],{"class":1407},[1397,7088,2792],{"class":1414},[1397,7090,2519],{"class":1403},[1397,7092,1520],{"class":1414},[1397,7094,1442],{"class":1403},[1397,7096,5353],{"class":1414},[1397,7098,7099],{"class":1403},"](config.topic, i), i)\n",[1397,7101,7102],{"class":1399,"line":2552},[1397,7103,3427],{"class":1403},[1397,7105,7106,7109,7111],{"class":1399,"line":2557},[1397,7107,7108],{"class":1403},"        .via(",[1397,7110,1378],{"class":1414},[1397,7112,7113],{"class":1403},".flexiFlow(producerSettings))\n",[1397,7115,7116],{"class":1399,"line":2575},[1397,7117,7118],{"class":1403},"        .runWith {\n",[1397,7120,7121,7124,7127,7129,7132,7134,7137,7140,7142,7145,7147],{"class":1399,"line":2580},[1397,7122,7123],{"class":1414},"          Sink",[1397,7125,7126],{"class":1403},".foreach(res ",[1397,7128,3969],{"class":1407},[1397,7130,7131],{"class":1403}," println(",[1397,7133,2764],{"class":1407},[1397,7135,7136],{"class":1438},"\"Wrote ",[1397,7138,7139],{"class":1403},"${res.passThrough}",[1397,7141,2711],{"class":1438},[1397,7143,7144],{"class":1403},"${config.topic}",[1397,7146,3420],{"class":1438},[1397,7148,2224],{"class":1403},[1397,7150,7151],{"class":1399,"line":2594},[1397,7152,3427],{"class":1403},[1698,7154,1729],{"id":3796},[211,7156,7157],{},"Again - the consumer is very similar up to the point we have successfully loaded a config.",[211,7159,7160],{},"Building the consumer settings is the same process with more fields:",[1317,7162,7164],{"className":2148,"code":7163,"language":1346,"meta":227,"style":227},"  private def buildConsumerSettings(sys: ActorSystem, config: Config) = {\n    val keyDeserializer = Serdes.String().deserializer()\n    val valueDeserializer = Serdes.Integer().deserializer().asInstanceOf[Deserializer[Int]]\n\n    ConsumerSettings(sys, keyDeserializer, valueDeserializer)\n      .withBootstrapServers(config.bootstrapServers)\n      .withProperties(\n        AUTO_OFFSET_RESET_CONFIG -> config.autoOffsetReset,\n        ENABLE_AUTO_COMMIT_CONFIG -> config.enableAutoCommit,\n        AUTO_COMMIT_INTERVAL_MS_CONFIG -> config.autoCommitIntervalMs\n      )\n      .withGroupId(config.groupId)\n      .withClientId(config.clientId)\n  }\n",[1296,7165,7166,7197,7215,7246,7250,7258,7262,7267,7278,7288,7298,7303,7308,7313],{"__ignoreMap":227},[1397,7167,7168,7170,7172,7175,7177,7179,7181,7183,7185,7187,7189,7191,7193,7195],{"class":1399,"line":1400},[1397,7169,6815],{"class":1407},[1397,7171,6818],{"class":1407},[1397,7173,7174],{"class":1414}," buildConsumerSettings",[1397,7176,1435],{"class":1403},[1397,7178,6826],{"class":2211},[1397,7180,2513],{"class":1403},[1397,7182,6831],{"class":1414},[1397,7184,1442],{"class":1403},[1397,7186,3984],{"class":2211},[1397,7188,2513],{"class":1403},[1397,7190,3926],{"class":1414},[1397,7192,3991],{"class":1403},[1397,7194,1408],{"class":1407},[1397,7196,2493],{"class":1403},[1397,7198,7199,7201,7204,7206,7208,7210,7212],{"class":1399,"line":228},[1397,7200,2560],{"class":1407},[1397,7202,7203],{"class":2211}," keyDeserializer",[1397,7205,2215],{"class":1407},[1397,7207,6857],{"class":1414},[1397,7209,1133],{"class":1403},[1397,7211,1520],{"class":1414},[1397,7213,7214],{"class":1403},"().deserializer()\n",[1397,7216,7217,7219,7222,7224,7226,7228,7230,7233,7235,7237,7240,7242,7244],{"class":1399,"line":1426},[1397,7218,2560],{"class":1407},[1397,7220,7221],{"class":2211}," valueDeserializer",[1397,7223,2215],{"class":1407},[1397,7225,6857],{"class":1414},[1397,7227,1133],{"class":1403},[1397,7229,6880],{"class":1414},[1397,7231,7232],{"class":1403},"().deserializer().",[1397,7234,6886],{"class":1561},[1397,7236,2519],{"class":1403},[1397,7238,7239],{"class":1414},"Deserializer",[1397,7241,2519],{"class":1403},[1397,7243,5353],{"class":1414},[1397,7245,6898],{"class":1403},[1397,7247,7248],{"class":1399,"line":1451},[1397,7249,1423],{"emptyLinePlaceholder":234},[1397,7251,7252,7255],{"class":1399,"line":1470},[1397,7253,7254],{"class":1414},"    ConsumerSettings",[1397,7256,7257],{"class":1403},"(sys, keyDeserializer, valueDeserializer)\n",[1397,7259,7260],{"class":1399,"line":1489},[1397,7261,6915],{"class":1403},[1397,7263,7264],{"class":1399,"line":1651},[1397,7265,7266],{"class":1403},"      .withProperties(\n",[1397,7268,7269,7272,7275],{"class":1399,"line":1675},[1397,7270,7271],{"class":1414},"        AUTO_OFFSET_RESET_CONFIG",[1397,7273,7274],{"class":1407}," ->",[1397,7276,7277],{"class":1403}," config.autoOffsetReset,\n",[1397,7279,7280,7283,7285],{"class":1399,"line":1687},[1397,7281,7282],{"class":1414},"        ENABLE_AUTO_COMMIT_CONFIG",[1397,7284,7274],{"class":1407},[1397,7286,7287],{"class":1403}," config.enableAutoCommit,\n",[1397,7289,7290,7293,7295],{"class":1399,"line":2275},[1397,7291,7292],{"class":1414},"        AUTO_COMMIT_INTERVAL_MS_CONFIG",[1397,7294,7274],{"class":1407},[1397,7296,7297],{"class":1403}," config.autoCommitIntervalMs\n",[1397,7299,7300],{"class":1399,"line":2281},[1397,7301,7302],{"class":1403},"      )\n",[1397,7304,7305],{"class":1399,"line":2540},[1397,7306,7307],{"class":1403},"      .withGroupId(config.groupId)\n",[1397,7309,7310],{"class":1399,"line":2552},[1397,7311,7312],{"class":1403},"      .withClientId(config.clientId)\n",[1397,7314,7315],{"class":1399,"line":2557},[1397,7316,2855],{"class":1403},[211,7318,7319],{},"Now we can define our subscription then set up the following akka stream:",[444,7321,7322,7325,7328],{},[447,7323,7324],{},"Source: a kafka implementation of an akka stream source that will read the kafka topic messages based on the configuration and subscription definition",[447,7326,7327],{},"Flow: extracts only the value",[447,7329,7330],{},"Sink: print what was seen",[1317,7332,7334],{"className":2148,"code":7333,"language":1346,"meta":227,"style":227},"      println(\"*** Starting Consumer ***\")\n\n      implicit val sys = ActorSystem()\n      implicit val mat = ActorMaterializer()\n\n      val consumerSettings: ConsumerSettings[String, Int] = buildConsumerSettings(sys, config)\n\n      val subscription = Subscriptions.topics(Set(config.topic))\n\n      Consumer\n        .plainSource[String, Int](consumerSettings, subscription)\n        .map(msg => msg.value())\n        .runForeach(w => println(s\"Consumed message with value $w\"))\n",[1296,7335,7336,7345,7349,7363,7377,7381,7408,7412,7433,7437,7442,7456,7466],{"__ignoreMap":227},[1397,7337,7338,7340,7343],{"class":1399,"line":1400},[1397,7339,2761],{"class":1403},[1397,7341,7342],{"class":1438},"\"*** Starting Consumer ***\"",[1397,7344,2549],{"class":1403},[1397,7346,7347],{"class":1399,"line":228},[1397,7348,1423],{"emptyLinePlaceholder":234},[1397,7350,7351,7353,7355,7357,7359,7361],{"class":1399,"line":1426},[1397,7352,6965],{"class":1407},[1397,7354,2208],{"class":1407},[1397,7356,6970],{"class":2211},[1397,7358,2215],{"class":1407},[1397,7360,5457],{"class":1414},[1397,7362,2572],{"class":1403},[1397,7364,7365,7367,7369,7371,7373,7375],{"class":1399,"line":1451},[1397,7366,6965],{"class":1407},[1397,7368,2208],{"class":1407},[1397,7370,6985],{"class":2211},[1397,7372,2215],{"class":1407},[1397,7374,5478],{"class":1414},[1397,7376,2572],{"class":1403},[1397,7378,7379],{"class":1399,"line":1470},[1397,7380,1423],{"emptyLinePlaceholder":234},[1397,7382,7383,7385,7388,7390,7393,7395,7397,7399,7401,7403,7405],{"class":1399,"line":1489},[1397,7384,2722],{"class":1407},[1397,7386,7387],{"class":2211}," consumerSettings",[1397,7389,1981],{"class":1407},[1397,7391,7392],{"class":1414}," ConsumerSettings",[1397,7394,2519],{"class":1403},[1397,7396,1520],{"class":1414},[1397,7398,1442],{"class":1403},[1397,7400,5353],{"class":1414},[1397,7402,3950],{"class":1403},[1397,7404,1408],{"class":1407},[1397,7406,7407],{"class":1403}," buildConsumerSettings(sys, config)\n",[1397,7409,7410],{"class":1399,"line":1651},[1397,7411,1423],{"emptyLinePlaceholder":234},[1397,7413,7414,7416,7419,7421,7424,7427,7430],{"class":1399,"line":1675},[1397,7415,2722],{"class":1407},[1397,7417,7418],{"class":2211}," subscription",[1397,7420,2215],{"class":1407},[1397,7422,7423],{"class":1414}," Subscriptions",[1397,7425,7426],{"class":1403},".topics(",[1397,7428,7429],{"class":1414},"Set",[1397,7431,7432],{"class":1403},"(config.topic))\n",[1397,7434,7435],{"class":1399,"line":1687},[1397,7436,1423],{"emptyLinePlaceholder":234},[1397,7438,7439],{"class":1399,"line":2275},[1397,7440,7441],{"class":1414},"      Consumer\n",[1397,7443,7444,7447,7449,7451,7453],{"class":1399,"line":2281},[1397,7445,7446],{"class":1403},"        .plainSource[",[1397,7448,1520],{"class":1414},[1397,7450,1442],{"class":1403},[1397,7452,5353],{"class":1414},[1397,7454,7455],{"class":1403},"](consumerSettings, subscription)\n",[1397,7457,7458,7461,7463],{"class":1399,"line":2540},[1397,7459,7460],{"class":1403},"        .map(msg ",[1397,7462,3969],{"class":1407},[1397,7464,7465],{"class":1403}," msg.value())\n",[1397,7467,7468,7471,7473,7475,7477,7480,7483,7485],{"class":1399,"line":2552},[1397,7469,7470],{"class":1403},"        .runForeach(w ",[1397,7472,3969],{"class":1407},[1397,7474,7131],{"class":1403},[1397,7476,2764],{"class":1407},[1397,7478,7479],{"class":1438},"\"Consumed message with value ",[1397,7481,7482],{"class":1403},"$w",[1397,7484,3420],{"class":1438},[1397,7486,2224],{"class":1403},[1698,7488,5146],{"id":5145},[211,7490,7491],{},"We can again use simple sbt commands to compile:",[1317,7493,7494],{"className":2869,"code":2870,"language":2871,"meta":227,"style":227},[1296,7495,7496],{"__ignoreMap":227},[1397,7497,7498,7500],{"class":1399,"line":1400},[1397,7499,2878],{"class":1414},[1397,7501,2881],{"class":1438},[211,7503,7504],{},"Run the producer:",[1317,7506,7508],{"className":2869,"code":7507,"language":2871,"meta":227,"style":227},"$ sbt run\n*** Starting Producer ***\nWrote 0 to akka-streams-topic\nWrote 2 to akka-streams-topic\n...\nWrote 19998 to akka-streams-topic\nWrote 20000 to akka-streams-topic\n",[1296,7509,7510,7519,7530,7543,7553,7557,7568],{"__ignoreMap":227},[1397,7511,7512,7514,7517],{"class":1399,"line":1400},[1397,7513,6420],{"class":1414},[1397,7515,7516],{"class":1438}," sbt",[1397,7518,2896],{"class":1438},[1397,7520,7521,7524,7527],{"class":1399,"line":228},[1397,7522,7523],{"class":1407},"***",[1397,7525,7526],{"class":1403}," Starting Producer ",[1397,7528,7529],{"class":1407},"***\n",[1397,7531,7532,7535,7537,7540],{"class":1399,"line":1426},[1397,7533,7534],{"class":1414},"Wrote",[1397,7536,5872],{"class":1561},[1397,7538,7539],{"class":1438}," to",[1397,7541,7542],{"class":1438}," akka-streams-topic\n",[1397,7544,7545,7547,7549,7551],{"class":1399,"line":1451},[1397,7546,7534],{"class":1414},[1397,7548,5866],{"class":1561},[1397,7550,7539],{"class":1438},[1397,7552,7542],{"class":1438},[1397,7554,7555],{"class":1399,"line":1470},[1397,7556,6498],{"class":1561},[1397,7558,7559,7561,7564,7566],{"class":1399,"line":1489},[1397,7560,7534],{"class":1414},[1397,7562,7563],{"class":1561}," 19998",[1397,7565,7539],{"class":1438},[1397,7567,7542],{"class":1438},[1397,7569,7570,7572,7575,7577],{"class":1399,"line":1651},[1397,7571,7534],{"class":1414},[1397,7573,7574],{"class":1561}," 20000",[1397,7576,7539],{"class":1438},[1397,7578,7542],{"class":1438},[211,7580,7581],{},"And then the consumer:",[1317,7583,7585],{"className":2869,"code":7584,"language":2871,"meta":227,"style":227},"$ sbt run\n*** Starting Basic Consumer ***\nConsumed message with value 0\nConsumed message with value 2\n...\nConsumed message with value 19998\nConsumed message with value 20000\n",[1296,7586,7587,7595,7604,7620,7632,7636,7649],{"__ignoreMap":227},[1397,7588,7589,7591,7593],{"class":1399,"line":1400},[1397,7590,6420],{"class":1414},[1397,7592,7516],{"class":1438},[1397,7594,2896],{"class":1438},[1397,7596,7597,7599,7602],{"class":1399,"line":228},[1397,7598,7523],{"class":1407},[1397,7600,7601],{"class":1403}," Starting Basic Consumer ",[1397,7603,7529],{"class":1407},[1397,7605,7606,7609,7612,7615,7617],{"class":1399,"line":1426},[1397,7607,7608],{"class":1414},"Consumed",[1397,7610,7611],{"class":1438}," message",[1397,7613,7614],{"class":1438}," with",[1397,7616,2742],{"class":1438},[1397,7618,7619],{"class":1561}," 0\n",[1397,7621,7622,7624,7626,7628,7630],{"class":1399,"line":1451},[1397,7623,7608],{"class":1414},[1397,7625,7611],{"class":1438},[1397,7627,7614],{"class":1438},[1397,7629,2742],{"class":1438},[1397,7631,6617],{"class":1561},[1397,7633,7634],{"class":1399,"line":1470},[1397,7635,6498],{"class":1561},[1397,7637,7638,7640,7642,7644,7646],{"class":1399,"line":1489},[1397,7639,7608],{"class":1414},[1397,7641,7611],{"class":1438},[1397,7643,7614],{"class":1438},[1397,7645,2742],{"class":1438},[1397,7647,7648],{"class":1561}," 19998\n",[1397,7650,7651,7653,7655,7657,7659],{"class":1399,"line":1651},[1397,7652,7608],{"class":1414},[1397,7654,7611],{"class":1438},[1397,7656,7614],{"class":1438},[1397,7658,2742],{"class":1438},[1397,7660,7661],{"class":1561}," 20000\n",[1254,7663,2061],{"id":2060},[211,7665,7666],{},"Moving to akka streams allows us to create our processing almost as a line by line recipe - and also handles the asynchronicity of the calls to the producer\u002Fconsumer in an akka streams context.",[211,7668,7669],{},"In this case - each recipe is simple - but in more complex situations you can use composition to be able to keep the code simple to understand.",[211,7671,7672],{},"The API for the consumer\u002Fproducer code now also looks very similar to the kafka streams API. Something for a future article perhaps?",[1254,7674,3515],{"id":3514},[444,7676,7677,7683],{},[447,7678,7679],{},[1118,7680,3524],{"href":7681,"rel":7682,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fakka-streams-kafka\u002Fproducer",[1122,1123,1124],[447,7684,7685],{},[1118,7686,3531],{"href":7687,"rel":7688,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Fkafka-java-to-scala\u002Ftree\u002Fmaster\u002Fakka-streams-kafka\u002Fconsumer",[1122,1123,1124],[2066,7690,7691],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":227,"searchDepth":228,"depth":228,"links":7693},[7694,7698,7703,7704],{"id":3984,"depth":228,"text":3926,"children":7695},[7696,7697],{"id":1377,"depth":1426,"text":1378},{"id":1728,"depth":1426,"text":1729},{"id":1296,"depth":228,"text":1297,"children":7699},[7700,7701,7702],{"id":3733,"depth":1426,"text":1378},{"id":3796,"depth":1426,"text":1729},{"id":5145,"depth":1426,"text":5146},{"id":2060,"depth":228,"text":2061},{"id":3514,"depth":228,"text":3515},"2019-05-15 12:10 +0200","In the previous post we took a look at akka streams in general. Let's apply that to our producer and consumer.",{"updated":1337},"\u002F2019\u002F05\u002F15\u002Fkafka-java-to-scala-akka-streams-kafka",{"title":6707,"description":1355},{"loc":7708},"2019\u002F05\u002F15\u002Fkafka-java-to-scala-akka-streams-kafka",[1346,6702,6703,1344,1377,1728],"c7EJGcbW07CzXtwQKAD7usl7BKoa138jU6b2J1GSJ3c",{"id":7715,"title":7716,"body":7717,"category":231,"date":7890,"description":7721,"embedImage":231,"extension":232,"image":7859,"intro":7891,"meta":7892,"navigation":234,"path":7893,"seo":7894,"series":7895,"sitemap":7896,"stem":7897,"tags":7898,"__hash__":7900},"content\u002F2014\u002F08\u002F03\u002Frun-rabbit-run.md","Run Rabbit Run",{"type":208,"value":7718,"toc":7888},[7719,7722,7725,7728,7734,7739,7745,7750,7756,7759,7762,7765,7771,7777,7786,7789,7795,7801,7807,7810,7816,7822,7825,7831,7836,7839,7845,7851,7854,7860,7866,7869,7875,7880,7885],[211,7720,7721],{},"We recently took over two female angora rabbits - while we were on holiday, they've had two weeks at the summer hut. Now we're back home and will need to leave them unattended - they need a run.",[211,7723,7724],{},"On Thursday evening I took a trip to Maxbo and purchased the wood (untreated in case the rabbits gnaw on it) and some plastic roofing plates - colour is called soot - hopefully it will provide some shade while still letting some light in.",[211,7726,7727],{},"On Friday - we started with building the frame:",[211,7729,7730],{},[217,7731],{"alt":7732,"src":7733},"Frame built","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fframe-1.jpg",[211,7735,7736],{},[217,7737],{"alt":7732,"src":7738},"\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fframe-2.jpg",[211,7740,7741],{},[217,7742],{"alt":7743,"src":7744},"Roof added","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Froof-1.jpg",[211,7746,7747],{},[217,7748],{"alt":7743,"src":7749},"\u002Fimages\u002Fposts\u002F2014\u002F08\u002Froof-2.jpg",[211,7751,7752],{},[217,7753],{"alt":7754,"src":7755},"Door in place","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fdoor.jpg",[211,7757,7758],{},"The white box in the background is for garden chair cushions but it's neither waterproof or mouse proof - so we're repurposing it as a bedroom for the rabbits. It will be joined with a short tunnel and can be properly insulated for winter - something the hutch can't be.",[211,7760,7761],{},"I'd intended to have enough netting in from the base to prevent the rabbits from digging out - but on Thursday evening while I was sitting in the garden a badger came round the corner of the garage. They're excellent diggers - so we needed a more robust base. But I didn't want the rabbits to have to sit on the wire mesh.",[211,7763,7764],{},"So the next stage was removing the turf:",[211,7766,7767],{},[217,7768],{"alt":7769,"src":7770},"Area marked up and turf being removed","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fremoving-turf.jpg",[211,7772,7773],{},[217,7774],{"alt":7775,"src":7776},"Turf removed","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fturf-removed.jpg",[211,7778,7779,7780,7785],{},"Now - finding fox proof netting turned out to be hard. In the end - we found a website for a firm based in Ramnes (near Tønsberg) ",[1118,7781,7784],{"href":7782,"rel":7783,"target":1125},"http:\u002F\u002Fwww.djohansenhusdyrutstyr.no\u002F",[1122,1123,1124],"D. Johansen Husdyrutstyr"," who had galvanised netting with a 1\" square grid - 1.2m high, 25m long and with 1,75mm thick wire. To top it off - they were doing a free delivery run around the region on Sunday. Perfect size, grid, thickness and timing.",[211,7787,7788],{},"Big heavy roll though.",[211,7790,7791],{},[217,7792],{"alt":7793,"src":7794},"Two strips to be made into the netting base","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fnetting-arrived-layout-of-base.jpg",[211,7796,7797],{},[217,7798],{"alt":7799,"src":7800},"Netting base made","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fbase-made.jpg",[211,7802,7803],{},[217,7804],{"alt":7805,"src":7806},"Base placed","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fbase-placed.jpg",[211,7808,7809],{},"Now we had to finish the ground preparation.",[211,7811,7812],{},[217,7813],{"alt":7814,"src":7815},"Placing the turf back in place","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Freplacing-turf.jpg",[211,7817,7818],{},[217,7819],{"alt":7820,"src":7821},"Turf replaced. You can also see the door added to the white box in the background","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fturf-replaced.jpg",[211,7823,7824],{},"Next the frame was moved back into place and the netting along the bottom fastened to it.",[211,7826,7827],{},[217,7828],{"alt":7829,"src":7830},"Frame placed inside ground grid","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Frun-placed-on-base-1.jpg",[211,7832,7833],{},[217,7834],{"alt":7829,"src":7835},"\u002Fimages\u002Fposts\u002F2014\u002F08\u002Frun-placed-on-base-2.jpg",[211,7837,7838],{},"Now came the walls.",[211,7840,7841],{},[217,7842],{"alt":7843,"src":7844},"Start of hanging the wall netting","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fstart-of-wall-netting.jpg",[211,7846,7847],{},[217,7848],{"alt":7849,"src":7850},"Wall netting in progress from the inside","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fwalls-in-progress.jpg",[211,7852,7853],{},"So - we got the walls and door done.",[211,7855,7856],{},[217,7857],{"alt":7858,"src":7859},"Walls and door complete","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fwalls-and-door-complete.jpg",[211,7861,7862],{},[217,7863],{"alt":7864,"src":7865},"Not a rabbit","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fnot-a-rabbit.jpg",[211,7867,7868],{},"And finally we moved the rabbits in.",[211,7870,7871],{},[217,7872],{"alt":7873,"src":7874},"Rabbits moved in","\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fmoved-in-1.jpg",[211,7876,7877],{},[217,7878],{"alt":7873,"src":7879},"\u002Fimages\u002Fposts\u002F2014\u002F08\u002Fmoved-in-2.jpg",[7881,7882],"youtube",{"id":7883,"title":7884},"YEYfDzuRNfg","Having a snack after moving in",[211,7886,7887],{},"What's left to do now? Well - we still need to make the tunnel to the white box\u002Fbedroom, add a hook in the inside of the door so that you can keep the door closed while inside - but otherwise - pretty much done.",{"title":227,"searchDepth":228,"depth":228,"links":7889},[],"2014-08-03 20:02 +0200","Building the rabbit run frame",{},"\u002F2014\u002F08\u002F03\u002Frun-rabbit-run",{"title":7716,"description":7721},"Rabbit Hutch-Run",{"loc":7893},"2014\u002F08\u002F03\u002Frun-rabbit-run",[7899],"rabbits","PMtFUnLgGNyuJzWOMfoxJdzON6W1G-s39NkcP07uoMo",{"id":7902,"title":7903,"body":7904,"category":231,"date":7974,"description":7908,"embedImage":231,"extension":232,"image":7971,"intro":7975,"meta":7976,"navigation":234,"path":7977,"seo":7978,"series":7895,"sitemap":7979,"stem":7980,"tags":7981,"__hash__":7982},"content\u002F2014\u002F10\u002F04\u002Fwarmer-rabbits.md","Warmer rabbits",{"type":208,"value":7905,"toc":7972},[7906,7909,7912,7915,7918,7924,7930,7936,7942,7948,7954,7960,7966],[211,7907,7908],{},"Finally got hold of the insulation for the rabbits bedroom.",[211,7910,7911],{},"It's 5cm thick expanded polystyrene. For now that's enough.",[211,7913,7914],{},"I have fitted a special low voltage heater that will keep the area between 12C and 15C - but we don't need that until later - and I'll only turn it on once I have temperature monitoring in place too.",[211,7916,7917],{},"The floor is waterproofed with damp-proofing membrane (since I have a roll lying around) - on top of that will go a layer of newspapers then straw.",[211,7919,7920],{},[217,7921],{"alt":7922,"src":7923},"Isolation - one end","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-1.jpg",[211,7925,7926],{},[217,7927],{"alt":7928,"src":7929},"Isolation - both ends and floor","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-2.jpg",[211,7931,7932],{},[217,7933],{"alt":7934,"src":7935},"Isolation - Floor and all walls","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-3.jpg",[211,7937,7938],{},[217,7939],{"alt":7940,"src":7941},"Floor fitted","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-4.jpg",[211,7943,7944],{},[217,7945],{"alt":7946,"src":7947},"Floor waterproofed, long walls fitted","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-5.jpg",[211,7949,7950],{},[217,7951],{"alt":7952,"src":7953},"All walls done, lid done","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-6.jpg",[211,7955,7956],{},[217,7957],{"alt":7958,"src":7959},"Heater fitted","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-7.jpg",[211,7961,7962],{},[217,7963],{"alt":7964,"src":7965},"Last bits of accessible polystyrene hidden away","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-8.jpg",[211,7967,7968],{},[217,7969],{"alt":7970,"src":7971},"Sleeping shelf added","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fstep-9.jpg",{"title":227,"searchDepth":228,"depth":228,"links":7973},[],"2014-10-04 18:38 +0200","Insulating the rabbit run sleeping area",{},"\u002F2014\u002F10\u002F04\u002Fwarmer-rabbits",{"title":7903,"description":7908},{"loc":7977},"2014\u002F10\u002F04\u002Fwarmer-rabbits",[7899],"_QW9uA-kS1aml8G6g0hitWB50NfFKTjeSm8ONGMqhyM",{"id":7984,"title":7985,"body":7986,"category":231,"date":8071,"description":7990,"embedImage":231,"extension":232,"image":8017,"intro":8072,"meta":8073,"navigation":234,"path":8074,"seo":8075,"series":7895,"sitemap":8076,"stem":8077,"tags":8078,"__hash__":8081},"content\u002F2014\u002F10\u002F14\u002Frabbit-bedroom-temperature-monitoring-with-arduino.md","Rabbit bedroom temperature monitoring with Arduino",{"type":208,"value":7987,"toc":8067},[7988,7991,7994,7997,8006,8009,8012,8018,8021,8027,8031,8057,8059],[211,7989,7990],{},"So - we've insulated the sleeping room (originally a garden cushion box) for the rabbits and mounted a heating panel that should keep it between 12˚C and 15˚C. But we need to keep an eye on it.",[211,7992,7993],{},"I could just get a cheap thermometer with remote unit from a hardware store - but how to check how the temperature varies over time?",[211,7995,7996],{},"Let's play with an arduino instead :)",[211,7998,7999,8000,8005],{},"I started with ",[1118,8001,8004],{"href":8002,"rel":8003,"target":1125},"http:\u002F\u002Fwww.raywenderlich.com\u002F38841\u002Farduino-tutorial-temperature-sensor",[1122,1123,1124],"this tutorial from raywenderlich.com",". It's using OneWire temperature sensors called DS18B20. I'm also going to use an ethernet connection with PoE to avoid having to have a separate power supply to the board.",[211,8007,8008],{},"I also modified the code by grabbing the latest onewire and dallas libraries and changed the output from JSON to a format that it is easy to write a munin script around.",[211,8010,8011],{},"The circuit I have ended up with is:",[211,8013,8014],{},[217,8015],{"alt":8016,"src":8017},"Circuit diagram","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fcircuit.png",[211,8019,8020],{},"And breadboarded for testing it looks like:",[211,8022,8023],{},[217,8024],{"alt":8025,"src":8026},"Breadboard circuit","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fbreadboard.jpg",[1698,8028,8030],{"id":8029},"components","Components",[444,8032,8033,8036,8039,8048,8051,8054],{},[447,8034,8035],{},"Arduino Uno",[447,8037,8038],{},"Arduino Ethernet Shield with PoE",[447,8040,8041,8042,8047],{},"2x DS18B20 (I chose ",[1118,8043,8046],{"href":8044,"rel":8045,"target":1125},"http:\u002F\u002Fwww.youblob.com\u002Fshop\u002Fproducts\u002Felectronics\u002Fsensors\u002Ftemperature\u002FSEN-11050",[1122,1123,1124],"these pre-cabled waterproofed ones"," to save time and make mounting easier)",[447,8049,8050],{},"2x 4k7Ω resistors",[447,8052,8053],{},"1x 330Ω resistor",[447,8055,8056],{},"1 LED",[1698,8058,1297],{"id":1296},[211,8060,8061,8062],{},"It's all ",[1118,8063,8066],{"href":8064,"rel":8065,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Frabbit-temperatures",[1122,1123,1124],"on github",{"title":227,"searchDepth":228,"depth":228,"links":8068},[8069,8070],{"id":8029,"depth":1426,"text":8030},{"id":1296,"depth":1426,"text":1297},"2014-10-14 08:31 +0200","How to keep an eye on the rabbit run heated sleeping area",{},"\u002F2014\u002F10\u002F14\u002Frabbit-bedroom-temperature-monitoring-with-arduino",{"title":7985,"description":7990},{"loc":8074},"2014\u002F10\u002F14\u002Frabbit-bedroom-temperature-monitoring-with-arduino",[7899,8079,8080],"arduino","DS18B20","-zJJL2sgKBze2lRrJAhOmscDvSfaqUq-GnEFlEBoDVA",{"id":8083,"title":8084,"body":8085,"category":231,"date":8128,"description":8089,"embedImage":231,"extension":232,"image":8107,"intro":8129,"meta":8130,"navigation":234,"path":8131,"seo":8132,"series":7895,"sitemap":8133,"stem":8134,"tags":8135,"__hash__":8136},"content\u002F2014\u002F10\u002F15\u002Frabbit-circuit-soldered-up.md","Rabbit circuit soldered up",{"type":208,"value":8086,"toc":8126},[8087,8090,8096,8099,8102,8108,8111,8114,8120,8123],[211,8088,8089],{},"So - the solder breadboards arrived today so I soldered up the circuitry. I've switched to using parasitic power mode for\nthe 1-wire temperature sensors (DS18B20's). To do this you tie both power and ground pins to ground, and add a 4k7 ohm resistor\nas a pull-up resistor between the data pin and 5v.",[211,8091,8092],{},[217,8093],{"alt":8094,"src":8095},"Circuit wired up in parasitic power mode","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fcircuit-parasitic.png",[211,8097,8098],{},"You can also run the devices in non-parasitic mode (they can return results faster in non-parasitic mode) but this requires three\nwires instead of two. You still need the pull-up 4k7 resistor too (this took me a while to figure out when I was testing).",[211,8100,8101],{},"To change this circuit to non-parasitic requires moving two wires. See the arrows in the next graphic.",[211,8103,8104],{},[217,8105],{"alt":8106,"src":8107},"Change required to switch to non-parasitic power mode","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fcircuit-non-parasitic.png",[211,8109,8110],{},"I do have one issue I can't solve right now.",[211,8112,8113],{},"If I connect this to a PoE switch then it takes power via the ethernet cable and it gives results - but the actual Arduino\nUno card gets extremely hot - this chip next to the power input connector:",[211,8115,8116],{},[217,8117],{"alt":8118,"src":8119},"Chip that gets hot","\u002Fimages\u002Fposts\u002F2014\u002F10\u002Fchip.jpg",[211,8121,8122],{},"If I connect the ethernet cable to a non PoE port and give the arduino power via USB then it does not get hot.",[211,8124,8125],{},"This annoys me somewhat - I chose the PoE ethernet shield so that I could still get temperature readings even if the\npower to the heaters failed - but I guess I can live with it while I try to find out what's going wrong - I can always\ntrigger an alert based on \"no data available\" instead of a too low temperature.",{"title":227,"searchDepth":228,"depth":228,"links":8127},[],"2014-10-15 23:04 +0200","Soldering up the rabbit temperature monitor",{},"\u002F2014\u002F10\u002F15\u002Frabbit-circuit-soldered-up",{"title":8084,"description":8089},{"loc":8131},"2014\u002F10\u002F15\u002Frabbit-circuit-soldered-up",[7899,8079,8080],"dPeprylQ7O6OPzsWJ3WPkhwKwqAb-AUka30PVANETLk",{"id":8138,"title":8139,"body":8140,"category":231,"date":8177,"description":8144,"embedImage":231,"extension":232,"image":8156,"intro":8178,"meta":8179,"navigation":234,"path":8180,"seo":8181,"series":7895,"sitemap":8182,"stem":8183,"tags":8184,"__hash__":8185},"content\u002F2014\u002F11\u002F22\u002Frebuild-rabbit-sensor-to-single-wire-bus.md","Rebuild rabbit sensor to single wire bus",{"type":208,"value":8141,"toc":8174},[8142,8145,8148,8151,8157,8160,8166,8168],[211,8143,8144],{},"OK - so I decided to move the arduino end of the temperature sensor to the other end of the connection - that is indoors.",[211,8146,8147],{},"This means that I use the ethernet cable as a simple onewire bus wire - not as an ethernet cable - and also means that I don't have to\nhave the arduino in a weatherproof box.",[211,8149,8150],{},"So the circuit design now looks like this:",[211,8152,8153],{},[217,8154],{"alt":8155,"src":8156},"Circuit Diagram","\u002Fimages\u002Fposts\u002F2014\u002F11\u002Fcircuit.png",[211,8158,8159],{},"And soldered up - you can see the red\u002Fblack power, blue (LED) and yellow (1wire bus) hookup wires that go to the arduino and the end of the network wire heading off to the sensors. The sensors themselves are connected in parallel with the sensor wire connected to blue, the ground to blue\u002Fwhite and the power to brown. No other wires in the ethernet cable are in use.",[211,8161,8162],{},[217,8163],{"alt":8164,"src":8165},"Wired up","\u002Fimages\u002Fposts\u002F2014\u002F11\u002Fbreadboard.jpg",[1698,8167,1297],{"id":1296},[211,8169,8170,8171],{},"All updated ",[1118,8172,8066],{"href":8064,"rel":8173,"target":1125},[1122,1123,1124],{"title":227,"searchDepth":228,"depth":228,"links":8175},[8176],{"id":1296,"depth":1426,"text":1297},"2014-11-22 12:42 +0100","Updating the temperature sensor for the outside rabbit run",{},"\u002F2014\u002F11\u002F22\u002Frebuild-rabbit-sensor-to-single-wire-bus",{"title":8139,"description":8144},{"loc":8180},"2014\u002F11\u002F22\u002Frebuild-rabbit-sensor-to-single-wire-bus",[7899,8079,8080],"iQ87qLF99P4PH4G6gQzvh7LOqmI0pXrTKSe10rDed1c",{"id":8187,"title":8188,"body":8189,"category":27,"date":8320,"description":8321,"embedImage":231,"extension":232,"image":231,"intro":8204,"meta":8322,"navigation":234,"path":8323,"seo":8324,"series":8325,"sitemap":8326,"stem":8327,"tags":8328,"__hash__":8335},"content\u002F2020\u002F03\u002F02\u002Frevisiting-the-sbanken-api-with-swiftui.md","Revisiting the S'banken API with SwiftUI",{"type":208,"value":8190,"toc":8316},[8191,8199,8202,8205,8208,8212,8221,8232,8235,8246,8252,8254,8258,8261,8272,8275,8278,8281,8307,8309],[211,8192,8193,8194,8198],{},"Back in September 2018 ",[1118,8195,8197],{"href":8196},"\u002F2018\u002F09\u002F26\u002Fpocket-money-with-the-s-banken-api\u002F","I wrote about using the new S'banken API"," to create a pocket money app for iOS - since they didn't have one at the time.",[211,8200,8201],{},"The still don't - but - iOS development has moved on a bit since then - notably with the release last year of SwiftUI.",[211,8203,8204],{},"In this series - we will take a look at creating the app from scratch - but using SwiftUI and some helper libraries.",[8206,8207],"hr",{},[1254,8209,8211],{"id":8210},"recap","Recap",[211,8213,8214,8215,8220],{},"The ",[1118,8216,8219],{"href":8217,"rel":8218,"target":1125},"https:\u002F\u002Fsecure.sbanken.no\u002FPersonal\u002FApiBeta\u002FInfo\u002F",[1122,1123,1124],"S'banken API"," provides several functions. In the previous application - we used:",[444,8222,8223,8226,8229],{},[447,8224,8225],{},"Authorization",[447,8227,8228],{},"Get list of accounts",[447,8230,8231],{},"Get account details",[211,8233,8234],{},"To do this - we needed 3 values:",[444,8236,8237,8240,8243],{},[447,8238,8239],{},"The client ID (applikasjonsnøkkel)",[447,8241,8242],{},"The client secret (generated on the developer portal - usually valid for 3 months)",[447,8244,8245],{},"The ID number of the account owner (fødselsnummer - 11 digits)",[211,8247,8248,8249,1133],{},"The authorization uses ",[1250,8250,8251],{},"client credentials grant flow",[8206,8253],{},[1254,8255,8257],{"id":8256},"the-plan","The plan",[211,8259,8260],{},"Entering the new client secret every 90 days got old quite fast. The new app will be provided with 4 values:",[444,8262,8263,8265,8267,8269],{},[447,8264,8239],{},[447,8266,8242],{},[447,8268,8245],{},[447,8270,8271],{},"The bank account ID",[211,8273,8274],{},"These will be provided via QR code (remember not to share the code) to simplify data entry",[211,8276,8277],{},"SwiftUI will be used instead of Swift+Storyboards. SwiftUI is not quite as feature complete as Swift+Storyboards yet - but it is fully usable.",[211,8279,8280],{},"We will make use of the new Swift Package Manager for dependencies. Initial libraries I expect to use are:",[444,8282,8283,8291,8299],{},[447,8284,8285,8290],{},[1118,8286,8289],{"href":8287,"rel":8288,"target":1125},"https:\u002F\u002Fgithub.com\u002FAlamofire\u002FAlamofire.git",[1122,1123,1124],"Alamofire"," - network calls (if S'banken adds their API library to Swift Package Manager instead of Carthage we'll revisit this)",[447,8292,8293,8298],{},[1118,8294,8297],{"href":8295,"rel":8296,"target":1125},"https:\u002F\u002Fgithub.com\u002Ftwostraws\u002FCodeScanner.git",[1122,1123,1124],"Code Scanner"," - wraps up the QR scanner in a nice swift UI component",[447,8300,8301,8306],{},[1118,8302,8305],{"href":8303,"rel":8304,"target":1125},"https:\u002F\u002Fgithub.com\u002Ftimbersoftware\u002FSwiftUIRefresh.git",[1122,1123,1124],"SwiftUI Refresh"," - implements pull to refresh for Swift UI",[8206,8308],{},[211,8310,8311],{},[1118,8312,8315],{"href":8313,"rel":8314,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Flommepenger-swiftui",[1122,1123,1124],"GitHub Repository",{"title":227,"searchDepth":228,"depth":228,"links":8317},[8318,8319],{"id":8210,"depth":228,"text":8211},{"id":8256,"depth":228,"text":8257},"2020-03-02 20:13 +0100","Back in September 2018 I wrote about using the new S'banken API to create a pocket money app for iOS - since they didn't have one at the time.",{},"\u002F2020\u002F03\u002F02\u002Frevisiting-the-sbanken-api-with-swiftui",{"title":8188,"description":8321},"Revisiting the Sbanken API with SwiftUI",{"loc":8323},"2020\u002F03\u002F02\u002Frevisiting-the-sbanken-api-with-swiftui",[8329,8330,8331,8332,8333,8334],"sbanken","open banking","ios","swift","swiftui","xcode","2wXasu_-rQ9HbM97ahJdvCdBGFPUCPWjTb9dHx5YYMM",{"id":8337,"title":8338,"body":8339,"category":27,"date":8517,"description":8343,"embedImage":231,"extension":232,"image":8370,"intro":8343,"meta":8518,"navigation":234,"path":8519,"seo":8520,"series":8325,"sitemap":8521,"stem":8522,"tags":8523,"__hash__":8524},"content\u002F2020\u002F03\u002F02\u002Fswiftui-project-setup.md","SwiftUI project setup",{"type":208,"value":8340,"toc":8511},[8341,8344,8347,8349,8353,8356,8365,8371,8374,8376,8380,8383,8398,8447,8449,8453,8456,8462,8469,8476,8479,8485,8491,8494,8496,8498,8501,8503,8508],[211,8342,8343],{},"Let's start by getting the correct project setup.",[211,8345,8346],{},"You'll need to be on the latest Xcode (11.3 or so) - and it will be best if you are running Catalina - then we can get the canvas previews in XCode to work.",[8206,8348],{},[1254,8350,8352],{"id":8351},"setup","Setup",[211,8354,8355],{},"In Xcode - start a new project. Choose iOS and a single view app.",[211,8357,8358,8359,8364],{},"Fill out the project details. In my case - I will end up keeping the same details as the ",[1118,8360,8363],{"href":8361,"rel":8362,"target":1125},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002FLommepenger\u002Ftree\u002F63a3bda3183926a18e821d90c62dc33b807c7e33",[1122,1123,1124],"previous version of the app",". Make sure to choose Swift as the language and SwiftUI as the User Interface.",[211,8366,8367],{},[217,8368],{"alt":8369,"src":8370},"Project Setup","\u002Fimages\u002Fposts\u002F2020\u002F03\u002Fproject-setup.png",[211,8372,8373],{},"Choose a place to save the project and enable git repository generation.",[8206,8375],{},[1254,8377,8379],{"id":8378},"gitignore",".gitignore",[211,8381,8382],{},"Xcode's git setup doesn't create a .gitignore file and it does have a bunch of files that we don't really want checked in. So - let's grab one.",[211,8384,8385,8386,8391,8392,8397],{},"GitHub maintains a really useful ",[1118,8387,8390],{"href":8388,"rel":8389,"target":1125},"https:\u002F\u002Fgithub.com\u002Fgithub\u002Fgitignore",[1122,1123,1124],"gitignore repository"," with lots of different templates. We'll grab the ",[1118,8393,8396],{"href":8394,"rel":8395,"target":1125},"https:\u002F\u002Fgithub.com\u002Fgithub\u002Fgitignore\u002Fblob\u002Fmaster\u002FSwift.gitignore",[1122,1123,1124],"Swift"," one and save it as .gitignore in the project (add and commit) before we continue.",[1317,8399,8401],{"className":2869,"code":8400,"language":2871,"meta":227,"style":227},"$ curl -o .gitignore https:\u002F\u002Fraw.githubusercontent.com\u002Fgithub\u002Fgitignore\u002Fmaster\u002FSwift.gitignore\n$ git add .gitignore\n$ git ci -m \"Add .gitignore\"\n",[1296,8402,8403,8419,8432],{"__ignoreMap":227},[1397,8404,8405,8407,8410,8413,8416],{"class":1399,"line":1400},[1397,8406,6420],{"class":1414},[1397,8408,8409],{"class":1438}," curl",[1397,8411,8412],{"class":1561}," -o",[1397,8414,8415],{"class":1438}," .gitignore",[1397,8417,8418],{"class":1438}," https:\u002F\u002Fraw.githubusercontent.com\u002Fgithub\u002Fgitignore\u002Fmaster\u002FSwift.gitignore\n",[1397,8420,8421,8423,8426,8429],{"class":1399,"line":228},[1397,8422,6420],{"class":1414},[1397,8424,8425],{"class":1438}," git",[1397,8427,8428],{"class":1438}," add",[1397,8430,8431],{"class":1438}," .gitignore\n",[1397,8433,8434,8436,8438,8441,8444],{"class":1399,"line":1426},[1397,8435,6420],{"class":1414},[1397,8437,8425],{"class":1438},[1397,8439,8440],{"class":1438}," ci",[1397,8442,8443],{"class":1561}," -m",[1397,8445,8446],{"class":1438}," \"Add .gitignore\"\n",[8206,8448],{},[1254,8450,8452],{"id":8451},"libraries","Libraries",[211,8454,8455],{},"Let's finish up the setup with two libraries we already know we want to add.",[211,8457,8458,8459,1133],{},"In Xcode - choose ",[1296,8460,8461],{},"File > Swift Packages > Add Package Dependency",[211,8463,8464,8465,8468],{},"In the package repository url field - enter ",[1118,8466,8287],{"href":8287,"rel":8467,"target":1125},[1122,1123,1124]," and hit next.",[211,8470,8471,8472,8475],{},"For version rule - keep ",[1296,8473,8474],{},"Up to next major"," and next again.",[211,8477,8478],{},"In the final window - make sure that the package is checked off for your app's target and hit finish.",[211,8480,8481],{},[217,8482],{"alt":8483,"src":8484},"Add package confirmation","\u002Fimages\u002Fposts\u002F2020\u002F03\u002Fadd-package.png",[211,8486,8487,8488],{},"Now do the same with ",[1118,8489,8295],{"href":8295,"rel":8490,"target":1125},[1122,1123,1124],[211,8492,8493],{},"Finally git add and commit the changes.",[8206,8495],{},[1254,8497,2061],{"id":2060},[211,8499,8500],{},"The project is now ready to use - in the next step we'll start by testing out QR code scanning.",[8206,8502],{},[211,8504,8505],{},[1118,8506,8315],{"href":8313,"rel":8507,"target":1125},[1122,1123,1124],[2066,8509,8510],{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":227,"searchDepth":228,"depth":228,"links":8512},[8513,8514,8515,8516],{"id":8351,"depth":228,"text":8352},{"id":8378,"depth":228,"text":8379},{"id":8451,"depth":228,"text":8452},{"id":2060,"depth":228,"text":2061},"2020-03-02 20:54 +0100",{},"\u002F2020\u002F03\u002F02\u002Fswiftui-project-setup",{"title":8338,"description":8343},{"loc":8519},"2020\u002F03\u002F02\u002Fswiftui-project-setup",[8331,8332,8333,8334,2087],"E0CktKsywHpakNZIth5O5jUsrYqvCCKT-jLMtCQ4L-0",{"id":8526,"title":8527,"body":8528,"category":27,"date":9505,"description":227,"embedImage":231,"extension":232,"image":8793,"intro":8536,"meta":9506,"navigation":234,"path":9507,"seo":9508,"series":8325,"sitemap":9509,"stem":9510,"tags":9511,"__hash__":9512},"content\u002F2020\u002F03\u002F03\u002Fscanning-qr-codes-with-swiftui.md","Scanning QR codes with SwiftUI",{"type":208,"value":8529,"toc":9496},[8530,8534,8537,8540,8602,8605,8608,8670,8673,8729,8731,8735,8741,8748,8751,8759,8785,8788,8794,8796,8800,8803,8811,8815,8818,8821,8824,8827,8851,8854,8861,8956,8959,8988,8991,9107,9110,9117,9123,9126,9128,9132,9135,9138,9141,9268,9271,9274,9479,9481,9483,9486,9488,9493],[1254,8531,8533],{"id":8532},"model","Model",[211,8535,8536],{},"So the first thing we want to do is to have a way to scan a QR code to get the relevant IDs for the app.",[211,8538,8539],{},"We will use a simple JSON format:",[1317,8541,8545],{"className":8542,"code":8543,"language":8544,"meta":227,"style":227},"language-json shiki shiki-themes github-dark","{\n  \"clientId\": \"application id from S'banken (applikasjonsnøkkel)\",\n  \"clientSecret\": \"application password from S'banken\",\n  \"userId\": \"national identity number (fødselsnummer)\",\n  \"accountNr\": \"bank account number\"\n}\n","json",[1296,8546,8547,8552,8564,8576,8588,8598],{"__ignoreMap":227},[1397,8548,8549],{"class":1399,"line":1400},[1397,8550,8551],{"class":1403},"{\n",[1397,8553,8554,8557,8559,8562],{"class":1399,"line":228},[1397,8555,8556],{"class":1561},"  \"clientId\"",[1397,8558,2513],{"class":1403},[1397,8560,8561],{"class":1438},"\"application id from S'banken (applikasjonsnøkkel)\"",[1397,8563,2242],{"class":1403},[1397,8565,8566,8569,8571,8574],{"class":1399,"line":1426},[1397,8567,8568],{"class":1561},"  \"clientSecret\"",[1397,8570,2513],{"class":1403},[1397,8572,8573],{"class":1438},"\"application password from S'banken\"",[1397,8575,2242],{"class":1403},[1397,8577,8578,8581,8583,8586],{"class":1399,"line":1451},[1397,8579,8580],{"class":1561},"  \"userId\"",[1397,8582,2513],{"class":1403},[1397,8584,8585],{"class":1438},"\"national identity number (fødselsnummer)\"",[1397,8587,2242],{"class":1403},[1397,8589,8590,8593,8595],{"class":1399,"line":1470},[1397,8591,8592],{"class":1561},"  \"accountNr\"",[1397,8594,2513],{"class":1403},[1397,8596,8597],{"class":1438},"\"bank account number\"\n",[1397,8599,8600],{"class":1399,"line":1489},[1397,8601,1690],{"class":1403},[211,8603,8604],{},"For this we will need a model class - and we need to be able to convert between the json and the class instance.",[211,8606,8607],{},"Add a swift file to the project - Config.swift - and create a class Config that conforms to the Codable protocol.",[1317,8609,8612],{"className":8610,"code":8611,"language":8332,"meta":227,"style":227},"language-swift shiki shiki-themes github-dark","class Config : Codable {\n    let clientId: String\n    let clientSecret: String\n    let userId: String\n    let accountNr: String\n}\n",[1296,8613,8614,8629,8639,8648,8657,8666],{"__ignoreMap":227},[1397,8615,8616,8619,8621,8624,8627],{"class":1399,"line":1400},[1397,8617,8618],{"class":1407},"class",[1397,8620,3749],{"class":1414},[1397,8622,8623],{"class":1403}," : ",[1397,8625,8626],{"class":1414},"Codable ",[1397,8628,8551],{"class":1403},[1397,8630,8631,8634,8637],{"class":1399,"line":228},[1397,8632,8633],{"class":1407},"    let",[1397,8635,8636],{"class":1403}," clientId: ",[1397,8638,3883],{"class":1561},[1397,8640,8641,8643,8646],{"class":1399,"line":1426},[1397,8642,8633],{"class":1407},[1397,8644,8645],{"class":1403}," clientSecret: ",[1397,8647,3883],{"class":1561},[1397,8649,8650,8652,8655],{"class":1399,"line":1451},[1397,8651,8633],{"class":1407},[1397,8653,8654],{"class":1403}," userId: ",[1397,8656,3883],{"class":1561},[1397,8658,8659,8661,8664],{"class":1399,"line":1470},[1397,8660,8633],{"class":1407},[1397,8662,8663],{"class":1403}," accountNr: ",[1397,8665,3883],{"class":1561},[1397,8667,8668],{"class":1399,"line":1489},[1397,8669,1690],{"class":1403},[211,8671,8672],{},"This will not compile yet - since we need to actually conform to Codable - so add the following enum to the class:",[1317,8674,8676],{"className":8610,"code":8675,"language":8332,"meta":227,"style":227},"enum CodingKeys: String, CodingKey {\n    case clientId\n    case clientSecret\n    case userId\n    case accountNr\n}\n",[1296,8677,8678,8697,8704,8711,8718,8725],{"__ignoreMap":227},[1397,8679,8680,8683,8686,8688,8690,8692,8695],{"class":1399,"line":1400},[1397,8681,8682],{"class":1407},"enum",[1397,8684,8685],{"class":1414}," CodingKeys",[1397,8687,2513],{"class":1403},[1397,8689,1520],{"class":1561},[1397,8691,1442],{"class":1403},[1397,8693,8694],{"class":1414},"CodingKey ",[1397,8696,8551],{"class":1403},[1397,8698,8699,8701],{"class":1399,"line":228},[1397,8700,3960],{"class":1407},[1397,8702,8703],{"class":1561}," clientId\n",[1397,8705,8706,8708],{"class":1399,"line":1426},[1397,8707,3960],{"class":1407},[1397,8709,8710],{"class":1561}," clientSecret\n",[1397,8712,8713,8715],{"class":1399,"line":1451},[1397,8714,3960],{"class":1407},[1397,8716,8717],{"class":1561}," userId\n",[1397,8719,8720,8722],{"class":1399,"line":1470},[1397,8721,3960],{"class":1407},[1397,8723,8724],{"class":1561}," accountNr\n",[1397,8726,8727],{"class":1399,"line":1489},[1397,8728,1690],{"class":1403},[8206,8730],{},[1254,8732,8734],{"id":8733},"qr-code-generation","QR Code Generation",[211,8736,8737],{},[8738,8739,8740],"strong",{},"Important: The JSON and the QR code contain keys and IDs and should not be shared or made publically available.",[211,8742,8743,8744,8747],{},"We'll start by creating a file ",[1296,8745,8746],{},"config.json"," that matches the format above but with the values filled out.",[211,8749,8750],{},"Now we need to create a QR code image for this file. We really don't want to use an online generator for this - since it has our secrets inside.",[211,8752,8753,8754],{},"I will be using a command line application called ",[1118,8755,8758],{"href":8756,"rel":8757,"target":1125},"https:\u002F\u002Fgithub.com\u002Ffukuchi\u002Flibqrencode",[1122,1123,1124],"qrencode",[1317,8760,8762],{"className":2869,"code":8761,"language":2871,"meta":227,"style":227},"qrencode -l H -o config.png \u003C config.json\n",[1296,8763,8764],{"__ignoreMap":227},[1397,8765,8766,8768,8771,8774,8776,8779,8782],{"class":1399,"line":1400},[1397,8767,8758],{"class":1414},[1397,8769,8770],{"class":1561}," -l",[1397,8772,8773],{"class":1438}," H",[1397,8775,8412],{"class":1561},[1397,8777,8778],{"class":1438}," config.png",[1397,8780,8781],{"class":1407}," \u003C",[1397,8783,8784],{"class":1438}," config.json\n",[211,8786,8787],{},"Here we ask for high error correction (we want to be sure that the code is correct). The command should generate a PNG image of the QR code in config.png.",[211,8789,8790],{},[217,8791],{"alt":8792,"src":8793},"title","\u002Fimages\u002Fposts\u002F2020\u002F03\u002Fconfig.png",[8206,8795],{},[1254,8797,8799],{"id":8798},"qr-code-scanner","QR Code Scanner",[211,8801,8802],{},"We need to do two things:",[444,8804,8805,8808],{},[447,8806,8807],{},"Handling showing\u002Fhiding the scanner",[447,8809,8810],{},"Handling the scan data received",[1698,8812,8814],{"id":8813},"scanner-sheet","Scanner Sheet",[211,8816,8817],{},"We will show the scanner in a sheet view. We'll start by working in the main ContentView.",[211,8819,8820],{},"We need to add an import to CodeScanner at the top - then we need to add a sheet.",[211,8822,8823],{},"The sheet will need a controlling state variable to handle show\u002Fhide.",[211,8825,8826],{},"Add the following variable to the view:",[1317,8828,8830],{"className":8610,"code":8829,"language":8332,"meta":227,"style":227},"@State private var showingScanner = false\n",[1296,8831,8832],{"__ignoreMap":227},[1397,8833,8834,8837,8840,8843,8846,8848],{"class":1399,"line":1400},[1397,8835,8836],{"class":1407},"@State",[1397,8838,8839],{"class":1407}," private",[1397,8841,8842],{"class":1407}," var",[1397,8844,8845],{"class":1403}," showingScanner ",[1397,8847,1408],{"class":1407},[1397,8849,8850],{"class":1561}," false\n",[211,8852,8853],{},"Now we need a button to trigger showing scanning - so let's add a simple button using the system image for QR code.",[211,8855,8856,8857,8860],{},"Replace the ",[1296,8858,8859],{},"Text(\"Hello World\")"," with the following:",[1317,8862,8864],{"className":8610,"code":8863,"language":8332,"meta":227,"style":227},"Button(action: {\n    self.showingScanner = true\n}) {\n    Image(systemName: \"qrcode\")\n        .resizable()\n        .frame(width: 32, height: 32)\n}\n",[1296,8865,8866,8879,8892,8897,8914,8924,8952],{"__ignoreMap":227},[1397,8867,8868,8871,8873,8876],{"class":1399,"line":1400},[1397,8869,8870],{"class":1561},"Button",[1397,8872,1435],{"class":1403},[1397,8874,8875],{"class":1561},"action",[1397,8877,8878],{"class":1403},": {\n",[1397,8880,8881,8884,8887,8889],{"class":1399,"line":228},[1397,8882,8883],{"class":1561},"    self",[1397,8885,8886],{"class":1403},".showingScanner ",[1397,8888,1408],{"class":1407},[1397,8890,8891],{"class":1561}," true\n",[1397,8893,8894],{"class":1399,"line":1426},[1397,8895,8896],{"class":1403},"}) {\n",[1397,8898,8899,8902,8904,8907,8909,8912],{"class":1399,"line":1451},[1397,8900,8901],{"class":1561},"    Image",[1397,8903,1435],{"class":1403},[1397,8905,8906],{"class":1561},"systemName",[1397,8908,2513],{"class":1403},[1397,8910,8911],{"class":1438},"\"qrcode\"",[1397,8913,2549],{"class":1403},[1397,8915,8916,8919,8922],{"class":1399,"line":1470},[1397,8917,8918],{"class":1403},"        .",[1397,8920,8921],{"class":1561},"resizable",[1397,8923,2572],{"class":1403},[1397,8925,8926,8928,8931,8933,8936,8938,8941,8943,8946,8948,8950],{"class":1399,"line":1489},[1397,8927,8918],{"class":1403},[1397,8929,8930],{"class":1561},"frame",[1397,8932,1435],{"class":1403},[1397,8934,8935],{"class":1561},"width",[1397,8937,2513],{"class":1403},[1397,8939,8940],{"class":1561},"32",[1397,8942,1442],{"class":1403},[1397,8944,8945],{"class":1561},"height",[1397,8947,2513],{"class":1403},[1397,8949,8940],{"class":1561},[1397,8951,2549],{"class":1403},[1397,8953,8954],{"class":1399,"line":1651},[1397,8955,1690],{"class":1403},[211,8957,8958],{},"Now - we need to attach a sheet to this view - we can add that to the button:",[1317,8960,8962],{"className":8610,"code":8961,"language":8332,"meta":227,"style":227},".sheet(isPresented: $showingScanner) {\n    ...\n}\n",[1296,8963,8964,8979,8984],{"__ignoreMap":227},[1397,8965,8966,8968,8971,8973,8976],{"class":1399,"line":1400},[1397,8967,1133],{"class":1403},[1397,8969,8970],{"class":1561},"sheet",[1397,8972,1435],{"class":1403},[1397,8974,8975],{"class":1561},"isPresented",[1397,8977,8978],{"class":1403},": $showingScanner) {\n",[1397,8980,8981],{"class":1399,"line":228},[1397,8982,8983],{"class":1407},"    ...\n",[1397,8985,8986],{"class":1399,"line":1426},[1397,8987,1690],{"class":1403},[211,8989,8990],{},"Then in the sheet we need our CodeScanner:",[1317,8992,8994],{"className":8610,"code":8993,"language":8332,"meta":227,"style":227}," CodeScannerView(codeTypes: [.qr], simulatedData: \"-\") { result in\n     switch result {\n     case .success(let code):\n         print(code)\n     case .failure(let error):\n         print(error)\n     }\n     self.showingScanner = false\n }\n",[1296,8995,8996,9023,9031,9053,9061,9079,9086,9091,9102],{"__ignoreMap":227},[1397,8997,8998,9001,9003,9006,9009,9012,9014,9017,9020],{"class":1399,"line":1400},[1397,8999,9000],{"class":1561}," CodeScannerView",[1397,9002,1435],{"class":1403},[1397,9004,9005],{"class":1561},"codeTypes",[1397,9007,9008],{"class":1403},": [.qr], ",[1397,9010,9011],{"class":1561},"simulatedData",[1397,9013,2513],{"class":1403},[1397,9015,9016],{"class":1438},"\"-\"",[1397,9018,9019],{"class":1403},") { result ",[1397,9021,9022],{"class":1407},"in\n",[1397,9024,9025,9028],{"class":1399,"line":228},[1397,9026,9027],{"class":1407},"     switch",[1397,9029,9030],{"class":1403}," result {\n",[1397,9032,9033,9036,9039,9042,9044,9047,9050],{"class":1399,"line":1426},[1397,9034,9035],{"class":1407},"     case",[1397,9037,9038],{"class":1403}," .",[1397,9040,9041],{"class":1561},"success",[1397,9043,1435],{"class":1403},[1397,9045,9046],{"class":1407},"let",[1397,9048,9049],{"class":1403}," code)",[1397,9051,9052],{"class":1407},":\n",[1397,9054,9055,9058],{"class":1399,"line":1451},[1397,9056,9057],{"class":1561},"         print",[1397,9059,9060],{"class":1403},"(code)\n",[1397,9062,9063,9065,9067,9070,9072,9074,9077],{"class":1399,"line":1470},[1397,9064,9035],{"class":1407},[1397,9066,9038],{"class":1403},[1397,9068,9069],{"class":1561},"failure",[1397,9071,1435],{"class":1403},[1397,9073,9046],{"class":1407},[1397,9075,9076],{"class":1403}," error)",[1397,9078,9052],{"class":1407},[1397,9080,9081,9083],{"class":1399,"line":1489},[1397,9082,9057],{"class":1561},[1397,9084,9085],{"class":1403},"(error)\n",[1397,9087,9088],{"class":1399,"line":1651},[1397,9089,9090],{"class":1403},"     }\n",[1397,9092,9093,9096,9098,9100],{"class":1399,"line":1675},[1397,9094,9095],{"class":1561},"     self",[1397,9097,8886],{"class":1403},[1397,9099,1408],{"class":1407},[1397,9101,8850],{"class":1561},[1397,9103,9104],{"class":1399,"line":1687},[1397,9105,9106],{"class":1403}," }\n",[211,9108,9109],{},"This just prints either the scanned data or the error message and sets the state so that the view closes. Note that the simulator has no camera access - so you need to set the simulatedData value to something for testingin the simulator.",[211,9111,9112,9113,9116],{},"Finally - we need to do one more thing - we need to have permission to use the camera. In the application's ",[1296,9114,9115],{},"Info.plist"," add a new entry to the dictionary:",[1317,9118,9121],{"className":9119,"code":9120,"language":1322},[1320],"\"Privacy - Camera Usage Description\" - \"Used to scan QR codes\"\n",[1296,9122,9120],{"__ignoreMap":227},[211,9124,9125],{},"Let's try - run it from Xcode on a device and scan the example QR code above - you should see the contents in the Xcode console.",[8206,9127],{},[1254,9129,9131],{"id":9130},"parsing","Parsing",[211,9133,9134],{},"So - we can now get the contents of the QR code as a json string.",[211,9136,9137],{},"The next step is to convert it to our Config model object.",[211,9139,9140],{},"We will use the swift JsonDecoder for this. We'll add this to the success block:",[1317,9142,9144],{"className":8610,"code":9143,"language":8332,"meta":227,"style":227},"if let data = code.data(using: .utf8) {\n    let decoder = JSONDecoder()\n\n    if let config = try? decoder.decode(Config.self, from: data) {\n        print(config.clientId)\n        print(config.clientSecret)\n        print(config.userId)\n        print(config.accountNr)\n    }\n}\n",[1296,9145,9146,9178,9192,9196,9231,9239,9246,9253,9260,9264],{"__ignoreMap":227},[1397,9147,9148,9151,9154,9157,9159,9162,9165,9167,9170,9173,9176],{"class":1399,"line":1400},[1397,9149,9150],{"class":1407},"if",[1397,9152,9153],{"class":1407}," let",[1397,9155,9156],{"class":1403}," data ",[1397,9158,1408],{"class":1407},[1397,9160,9161],{"class":1403}," code.",[1397,9163,9164],{"class":1561},"data",[1397,9166,1435],{"class":1403},[1397,9168,9169],{"class":1561},"using",[1397,9171,9172],{"class":1403},": .",[1397,9174,9175],{"class":1561},"utf8",[1397,9177,1580],{"class":1403},[1397,9179,9180,9182,9185,9187,9190],{"class":1399,"line":228},[1397,9181,8633],{"class":1407},[1397,9183,9184],{"class":1403}," decoder ",[1397,9186,1408],{"class":1407},[1397,9188,9189],{"class":1561}," JSONDecoder",[1397,9191,2572],{"class":1403},[1397,9193,9194],{"class":1399,"line":1426},[1397,9195,1423],{"emptyLinePlaceholder":234},[1397,9197,9198,9201,9203,9206,9208,9211,9214,9217,9220,9223,9225,9228],{"class":1399,"line":1451},[1397,9199,9200],{"class":1407},"    if",[1397,9202,9153],{"class":1407},[1397,9204,9205],{"class":1403}," config ",[1397,9207,1408],{"class":1407},[1397,9209,9210],{"class":1407}," try?",[1397,9212,9213],{"class":1403}," decoder.",[1397,9215,9216],{"class":1561},"decode",[1397,9218,9219],{"class":1403},"(Config.",[1397,9221,9222],{"class":1407},"self",[1397,9224,1442],{"class":1403},[1397,9226,9227],{"class":1561},"from",[1397,9229,9230],{"class":1403},": data) {\n",[1397,9232,9233,9236],{"class":1399,"line":1470},[1397,9234,9235],{"class":1561},"        print",[1397,9237,9238],{"class":1403},"(config.clientId)\n",[1397,9240,9241,9243],{"class":1399,"line":1489},[1397,9242,9235],{"class":1561},[1397,9244,9245],{"class":1403},"(config.clientSecret)\n",[1397,9247,9248,9250],{"class":1399,"line":1651},[1397,9249,9235],{"class":1561},[1397,9251,9252],{"class":1403},"(config.userId)\n",[1397,9254,9255,9257],{"class":1399,"line":1675},[1397,9256,9235],{"class":1561},[1397,9258,9259],{"class":1403},"(config.accountNr)\n",[1397,9261,9262],{"class":1399,"line":1687},[1397,9263,2023],{"class":1403},[1397,9265,9266],{"class":1399,"line":2275},[1397,9267,1690],{"class":1403},[211,9269,9270],{},"Try scanning again and you should see each field in the Xcode console.",[211,9272,9273],{},"The CodeScannerView now looks like this:",[1317,9275,9277],{"className":8610,"code":9276,"language":8332,"meta":227,"style":227}," CodeScannerView(codeTypes: [.qr], simulatedData: \"-\") { result in\n     switch result {\n     case .success(let code):\n         print(code)\n\n         if let data = code.data(using: .utf8) {\n             let decoder = JSONDecoder()\n\n             if let config = try? decoder.decode(Config.self, from: data) {\n                 print(config.clientId)\n                 print(config.clientSecret)\n                 print(config.userId)\n                 print(config.accountNr)\n             }\n         }\n\n     case .failure(let error):\n         print(error)\n     }\n     self.showingScanner = false\n }\n",[1296,9278,9279,9299,9305,9321,9327,9331,9356,9369,9373,9400,9407,9413,9419,9425,9430,9435,9439,9455,9461,9465,9475],{"__ignoreMap":227},[1397,9280,9281,9283,9285,9287,9289,9291,9293,9295,9297],{"class":1399,"line":1400},[1397,9282,9000],{"class":1561},[1397,9284,1435],{"class":1403},[1397,9286,9005],{"class":1561},[1397,9288,9008],{"class":1403},[1397,9290,9011],{"class":1561},[1397,9292,2513],{"class":1403},[1397,9294,9016],{"class":1438},[1397,9296,9019],{"class":1403},[1397,9298,9022],{"class":1407},[1397,9300,9301,9303],{"class":1399,"line":228},[1397,9302,9027],{"class":1407},[1397,9304,9030],{"class":1403},[1397,9306,9307,9309,9311,9313,9315,9317,9319],{"class":1399,"line":1426},[1397,9308,9035],{"class":1407},[1397,9310,9038],{"class":1403},[1397,9312,9041],{"class":1561},[1397,9314,1435],{"class":1403},[1397,9316,9046],{"class":1407},[1397,9318,9049],{"class":1403},[1397,9320,9052],{"class":1407},[1397,9322,9323,9325],{"class":1399,"line":1451},[1397,9324,9057],{"class":1561},[1397,9326,9060],{"class":1403},[1397,9328,9329],{"class":1399,"line":1470},[1397,9330,1423],{"emptyLinePlaceholder":234},[1397,9332,9333,9336,9338,9340,9342,9344,9346,9348,9350,9352,9354],{"class":1399,"line":1489},[1397,9334,9335],{"class":1407},"         if",[1397,9337,9153],{"class":1407},[1397,9339,9156],{"class":1403},[1397,9341,1408],{"class":1407},[1397,9343,9161],{"class":1403},[1397,9345,9164],{"class":1561},[1397,9347,1435],{"class":1403},[1397,9349,9169],{"class":1561},[1397,9351,9172],{"class":1403},[1397,9353,9175],{"class":1561},[1397,9355,1580],{"class":1403},[1397,9357,9358,9361,9363,9365,9367],{"class":1399,"line":1651},[1397,9359,9360],{"class":1407},"             let",[1397,9362,9184],{"class":1403},[1397,9364,1408],{"class":1407},[1397,9366,9189],{"class":1561},[1397,9368,2572],{"class":1403},[1397,9370,9371],{"class":1399,"line":1675},[1397,9372,1423],{"emptyLinePlaceholder":234},[1397,9374,9375,9378,9380,9382,9384,9386,9388,9390,9392,9394,9396,9398],{"class":1399,"line":1687},[1397,9376,9377],{"class":1407},"             if",[1397,9379,9153],{"class":1407},[1397,9381,9205],{"class":1403},[1397,9383,1408],{"class":1407},[1397,9385,9210],{"class":1407},[1397,9387,9213],{"class":1403},[1397,9389,9216],{"class":1561},[1397,9391,9219],{"class":1403},[1397,9393,9222],{"class":1407},[1397,9395,1442],{"class":1403},[1397,9397,9227],{"class":1561},[1397,9399,9230],{"class":1403},[1397,9401,9402,9405],{"class":1399,"line":2275},[1397,9403,9404],{"class":1561},"                 print",[1397,9406,9238],{"class":1403},[1397,9408,9409,9411],{"class":1399,"line":2281},[1397,9410,9404],{"class":1561},[1397,9412,9245],{"class":1403},[1397,9414,9415,9417],{"class":1399,"line":2540},[1397,9416,9404],{"class":1561},[1397,9418,9252],{"class":1403},[1397,9420,9421,9423],{"class":1399,"line":2552},[1397,9422,9404],{"class":1561},[1397,9424,9259],{"class":1403},[1397,9426,9427],{"class":1399,"line":2557},[1397,9428,9429],{"class":1403},"             }\n",[1397,9431,9432],{"class":1399,"line":2575},[1397,9433,9434],{"class":1403},"         }\n",[1397,9436,9437],{"class":1399,"line":2580},[1397,9438,1423],{"emptyLinePlaceholder":234},[1397,9440,9441,9443,9445,9447,9449,9451,9453],{"class":1399,"line":2594},[1397,9442,9035],{"class":1407},[1397,9444,9038],{"class":1403},[1397,9446,9069],{"class":1561},[1397,9448,1435],{"class":1403},[1397,9450,9046],{"class":1407},[1397,9452,9076],{"class":1403},[1397,9454,9052],{"class":1407},[1397,9456,9457,9459],{"class":1399,"line":2607},[1397,9458,9057],{"class":1561},[1397,9460,9085],{"class":1403},[1397,9462,9463],{"class":1399,"line":2627},[1397,9464,9090],{"class":1403},[1397,9466,9467,9469,9471,9473],{"class":1399,"line":2644},[1397,9468,9095],{"class":1561},[1397,9470,8886],{"class":1403},[1397,9472,1408],{"class":1407},[1397,9474,8850],{"class":1561},[1397,9476,9477],{"class":1399,"line":2649},[1397,9478,9106],{"class":1403},[8206,9480],{},[1254,9482,2061],{"id":2060},[211,9484,9485],{},"So - we can now get the configuration from a QR code - the next step will be to tidy the code a little, give the user a way to cancel the scan dialog and to persist the configuration.",[8206,9487],{},[211,9489,9490],{},[1118,9491,8315],{"href":8313,"rel":9492,"target":1125},[1122,1123,1124],[2066,9494,9495],{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":227,"searchDepth":228,"depth":228,"links":9497},[9498,9499,9500,9503,9504],{"id":8532,"depth":228,"text":8533},{"id":8733,"depth":228,"text":8734},{"id":8798,"depth":228,"text":8799,"children":9501},[9502],{"id":8813,"depth":1426,"text":8814},{"id":9130,"depth":228,"text":9131},{"id":2060,"depth":228,"text":2061},"2020-03-03 11:28 +0100",{},"\u002F2020\u002F03\u002F03\u002Fscanning-qr-codes-with-swiftui",{"title":8527,"description":227},{"loc":9507},"2020\u002F03\u002F03\u002Fscanning-qr-codes-with-swiftui",[8331,8332,8333,8334],"LMf7oS_yHbcJVRPwqyuXfT9-fRiwhdNSQlcWp0yL2i4",{"id":9514,"title":9515,"body":9516,"category":27,"date":10916,"description":9520,"embedImage":231,"extension":232,"image":231,"intro":9520,"meta":10917,"navigation":234,"path":10918,"seo":10919,"series":8325,"sitemap":10920,"stem":10921,"tags":10922,"__hash__":10923},"content\u002F2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain.md","View refactor and save to keychain",{"type":208,"value":9517,"toc":10903},[9518,9521,9523,9527,9530,9533,9541,9544,9548,9551,9554,9573,9580,9584,9587,9590,9593,9596,9599,9691,9694,9697,10021,10024,10026,10030,10033,10044,10047,10051,10054,10257,10261,10269,10276,10279,10512,10515,10519,10541,10544,10618,10622,10625,10628,10883,10886,10888,10890,10893,10895,10900],[211,9519,9520],{},"At this stage - we've a lot of stuff in the main ContentView. Let's do something about that first.",[8206,9522],{},[1254,9524,9526],{"id":9525},"refactor","Refactor",[211,9528,9529],{},"We'll start by moving the CodeScanner stuff to its own view. We'll also give the user a cancellation option so that they can clear the view without scanning.",[211,9531,9532],{},"To do this we need two things:",[444,9534,9535,9538],{},[447,9536,9537],{},"A way to close the view",[447,9539,9540],{},"A way to return the new data",[211,9542,9543],{},"Both of these could be done by passing in a state variable from the controlling view. However - we'll take a look at some other options.",[1698,9545,9547],{"id":9546},"closing-the-view","Closing the view",[211,9549,9550],{},"A view can get some information on its presentation from the environment.",[211,9552,9553],{},"In the new view - we can add this to the struct:",[1317,9555,9557],{"className":8610,"code":9556,"language":8332,"meta":227,"style":227},"Environment(\\.presentationMode) var presentation\n",[1296,9558,9559],{"__ignoreMap":227},[1397,9560,9561,9564,9567,9570],{"class":1399,"line":1400},[1397,9562,9563],{"class":1561},"Environment",[1397,9565,9566],{"class":1403},"(\\.presentationMode) ",[1397,9568,9569],{"class":1407},"var",[1397,9571,9572],{"class":1403}," presentation\n",[211,9574,9575,9576,9579],{},"Then anywhere we need to close the view - we can call ",[1296,9577,9578],{},"self.presentation.wrappedValue.dismiss()",". In this case - where we used a sheet with a controlling state variable for show\u002Fhide it will update this state for us.",[1698,9581,9583],{"id":9582},"returning-the-value","Returning the value",[211,9585,9586],{},"There are several possibilities here. We could pass in a state variable from the ContentView as a Binding - but - we want to do something with it when the value changes - and what we want to do is not really the concern of the ScannerView - it simply should scan and return the data.",[211,9588,9589],{},"A variable marked @State can't have hooks on its change events (willSet\u002FdidSet). We could get around this by using the ObservableObject support.",[211,9591,9592],{},"But - is this value actually a form of state for the content view? Not really. There is no \"initial state\" to send in and on return we want to save it to a data store (the retrieved value from the store is likely app state - but not the string representation of the scanned data itself).",[211,9594,9595],{},"There is a third option (callback closure) - but - can we use Binding to our advantage here?",[211,9597,9598],{},"Consider the following code:",[1317,9600,9602],{"className":8610,"code":9601,"language":8332,"meta":227,"style":227},"ScannerView(scannedData: Binding(\n    get: { \"\" },\n    set: self.newScanData\n))\n\n...\n\nfunc newScanData(_ code: String) {\n    ...\n}\n",[1296,9603,9604,9621,9635,9647,9651,9655,9659,9663,9683,9687],{"__ignoreMap":227},[1397,9605,9606,9609,9611,9614,9616,9619],{"class":1399,"line":1400},[1397,9607,9608],{"class":1561},"ScannerView",[1397,9610,1435],{"class":1403},[1397,9612,9613],{"class":1561},"scannedData",[1397,9615,2513],{"class":1403},[1397,9617,9618],{"class":1561},"Binding",[1397,9620,2256],{"class":1403},[1397,9622,9623,9626,9629,9632],{"class":1399,"line":228},[1397,9624,9625],{"class":1561},"    get",[1397,9627,9628],{"class":1403},": { ",[1397,9630,9631],{"class":1438},"\"\"",[1397,9633,9634],{"class":1403}," },\n",[1397,9636,9637,9640,9642,9644],{"class":1399,"line":1426},[1397,9638,9639],{"class":1561},"    set",[1397,9641,2513],{"class":1403},[1397,9643,9222],{"class":1561},[1397,9645,9646],{"class":1403},".newScanData\n",[1397,9648,9649],{"class":1399,"line":1451},[1397,9650,2224],{"class":1403},[1397,9652,9653],{"class":1399,"line":1470},[1397,9654,1423],{"emptyLinePlaceholder":234},[1397,9656,9657],{"class":1399,"line":1489},[1397,9658,6498],{"class":1407},[1397,9660,9661],{"class":1399,"line":1651},[1397,9662,1423],{"emptyLinePlaceholder":234},[1397,9664,9665,9668,9671,9673,9676,9679,9681],{"class":1399,"line":1675},[1397,9666,9667],{"class":1407},"func",[1397,9669,9670],{"class":1414}," newScanData",[1397,9672,1435],{"class":1403},[1397,9674,9675],{"class":1414},"_",[1397,9677,9678],{"class":1403}," code: ",[1397,9680,1520],{"class":1561},[1397,9682,1580],{"class":1403},[1397,9684,9685],{"class":1399,"line":1687},[1397,9686,8983],{"class":1407},[1397,9688,9689],{"class":1399,"line":2275},[1397,9690,1690],{"class":1403},[211,9692,9693],{},"This allows us to specify in the scanner view a normal binding and just set it on scan success - which will call the set here and hand off the value to the newScanData function.",[211,9695,9696],{},"So - our new ScannerView (with added title and cancel button) looks like this:",[1317,9698,9700],{"className":8610,"code":9699,"language":8332,"meta":227,"style":227},"struct ScannerView: View {\n    @Environment(\\.presentationMode) var presentation\n\n    @Binding var scannedData : String\n\n    var body: some View {\n        VStack {\n            HStack {\n                Spacer()\n                Text(\"Scan\").font(.title).padding(.leading, 16.0)\n                Spacer()\n                Image(systemName: \"xmark.square\")\n                    .resizable()\n                    .frame(width: 32, height: 32)\n                    .onTapGesture {\n                        self.presentation.wrappedValue.dismiss()\n                    }\n            }.padding()\n\n            CodeScannerView(codeTypes: [.qr], simulatedData: \"-\") { result in\n                switch result {\n                case .success(let code):\n                    self.scannedData = code\n                case .failure(let error):\n                    print(error)\n                }\n                self.presentation.wrappedValue.dismiss()\n            }\n        }\n    }\n}\n",[1296,9701,9702,9717,9728,9732,9744,9748,9762,9769,9776,9783,9812,9818,9834,9843,9867,9876,9889,9894,9903,9907,9928,9935,9952,9965,9981,9988,9993,10004,10009,10013,10017],{"__ignoreMap":227},[1397,9703,9704,9707,9710,9712,9715],{"class":1399,"line":1400},[1397,9705,9706],{"class":1407},"struct",[1397,9708,9709],{"class":1414}," ScannerView",[1397,9711,2513],{"class":1403},[1397,9713,9714],{"class":1414},"View ",[1397,9716,8551],{"class":1403},[1397,9718,9719,9722,9724,9726],{"class":1399,"line":228},[1397,9720,9721],{"class":1407},"    @Environment",[1397,9723,9566],{"class":1403},[1397,9725,9569],{"class":1407},[1397,9727,9572],{"class":1403},[1397,9729,9730],{"class":1399,"line":1426},[1397,9731,1423],{"emptyLinePlaceholder":234},[1397,9733,9734,9737,9739,9742],{"class":1399,"line":1451},[1397,9735,9736],{"class":1407},"    @Binding",[1397,9738,8842],{"class":1407},[1397,9740,9741],{"class":1403}," scannedData : ",[1397,9743,3883],{"class":1561},[1397,9745,9746],{"class":1399,"line":1470},[1397,9747,1423],{"emptyLinePlaceholder":234},[1397,9749,9750,9753,9756,9759],{"class":1399,"line":1489},[1397,9751,9752],{"class":1407},"    var",[1397,9754,9755],{"class":1403}," body: ",[1397,9757,9758],{"class":1407},"some",[1397,9760,9761],{"class":1403}," View {\n",[1397,9763,9764,9767],{"class":1399,"line":1651},[1397,9765,9766],{"class":1561},"        VStack",[1397,9768,2493],{"class":1403},[1397,9770,9771,9774],{"class":1399,"line":1675},[1397,9772,9773],{"class":1561},"            HStack",[1397,9775,2493],{"class":1403},[1397,9777,9778,9781],{"class":1399,"line":1687},[1397,9779,9780],{"class":1561},"                Spacer",[1397,9782,2572],{"class":1403},[1397,9784,9785,9788,9790,9793,9795,9798,9801,9804,9807,9810],{"class":1399,"line":2275},[1397,9786,9787],{"class":1561},"                Text",[1397,9789,1435],{"class":1403},[1397,9791,9792],{"class":1438},"\"Scan\"",[1397,9794,1713],{"class":1403},[1397,9796,9797],{"class":1561},"font",[1397,9799,9800],{"class":1403},"(.title).",[1397,9802,9803],{"class":1561},"padding",[1397,9805,9806],{"class":1403},"(.leading, ",[1397,9808,9809],{"class":1561},"16.0",[1397,9811,2549],{"class":1403},[1397,9813,9814,9816],{"class":1399,"line":2281},[1397,9815,9780],{"class":1561},[1397,9817,2572],{"class":1403},[1397,9819,9820,9823,9825,9827,9829,9832],{"class":1399,"line":2540},[1397,9821,9822],{"class":1561},"                Image",[1397,9824,1435],{"class":1403},[1397,9826,8906],{"class":1561},[1397,9828,2513],{"class":1403},[1397,9830,9831],{"class":1438},"\"xmark.square\"",[1397,9833,2549],{"class":1403},[1397,9835,9836,9839,9841],{"class":1399,"line":2552},[1397,9837,9838],{"class":1403},"                    .",[1397,9840,8921],{"class":1561},[1397,9842,2572],{"class":1403},[1397,9844,9845,9847,9849,9851,9853,9855,9857,9859,9861,9863,9865],{"class":1399,"line":2557},[1397,9846,9838],{"class":1403},[1397,9848,8930],{"class":1561},[1397,9850,1435],{"class":1403},[1397,9852,8935],{"class":1561},[1397,9854,2513],{"class":1403},[1397,9856,8940],{"class":1561},[1397,9858,1442],{"class":1403},[1397,9860,8945],{"class":1561},[1397,9862,2513],{"class":1403},[1397,9864,8940],{"class":1561},[1397,9866,2549],{"class":1403},[1397,9868,9869,9871,9874],{"class":1399,"line":2575},[1397,9870,9838],{"class":1403},[1397,9872,9873],{"class":1561},"onTapGesture",[1397,9875,2493],{"class":1403},[1397,9877,9878,9881,9884,9887],{"class":1399,"line":2580},[1397,9879,9880],{"class":1561},"                        self",[1397,9882,9883],{"class":1403},".presentation.wrappedValue.",[1397,9885,9886],{"class":1561},"dismiss",[1397,9888,2572],{"class":1403},[1397,9890,9891],{"class":1399,"line":2594},[1397,9892,9893],{"class":1403},"                    }\n",[1397,9895,9896,9899,9901],{"class":1399,"line":2607},[1397,9897,9898],{"class":1403},"            }.",[1397,9900,9803],{"class":1561},[1397,9902,2572],{"class":1403},[1397,9904,9905],{"class":1399,"line":2627},[1397,9906,1423],{"emptyLinePlaceholder":234},[1397,9908,9909,9912,9914,9916,9918,9920,9922,9924,9926],{"class":1399,"line":2644},[1397,9910,9911],{"class":1561},"            CodeScannerView",[1397,9913,1435],{"class":1403},[1397,9915,9005],{"class":1561},[1397,9917,9008],{"class":1403},[1397,9919,9011],{"class":1561},[1397,9921,2513],{"class":1403},[1397,9923,9016],{"class":1438},[1397,9925,9019],{"class":1403},[1397,9927,9022],{"class":1407},[1397,9929,9930,9933],{"class":1399,"line":2649},[1397,9931,9932],{"class":1407},"                switch",[1397,9934,9030],{"class":1403},[1397,9936,9937,9940,9942,9944,9946,9948,9950],{"class":1399,"line":2675},[1397,9938,9939],{"class":1407},"                case",[1397,9941,9038],{"class":1403},[1397,9943,9041],{"class":1561},[1397,9945,1435],{"class":1403},[1397,9947,9046],{"class":1407},[1397,9949,9049],{"class":1403},[1397,9951,9052],{"class":1407},[1397,9953,9954,9957,9960,9962],{"class":1399,"line":2680},[1397,9955,9956],{"class":1561},"                    self",[1397,9958,9959],{"class":1403},".scannedData ",[1397,9961,1408],{"class":1407},[1397,9963,9964],{"class":1403}," code\n",[1397,9966,9967,9969,9971,9973,9975,9977,9979],{"class":1399,"line":2693},[1397,9968,9939],{"class":1407},[1397,9970,9038],{"class":1403},[1397,9972,9069],{"class":1561},[1397,9974,1435],{"class":1403},[1397,9976,9046],{"class":1407},[1397,9978,9076],{"class":1403},[1397,9980,9052],{"class":1407},[1397,9982,9983,9986],{"class":1399,"line":2698},[1397,9984,9985],{"class":1561},"                    print",[1397,9987,9085],{"class":1403},[1397,9989,9990],{"class":1399,"line":2719},[1397,9991,9992],{"class":1403},"                }\n",[1397,9994,9995,9998,10000,10002],{"class":1399,"line":2737},[1397,9996,9997],{"class":1561},"                self",[1397,9999,9883],{"class":1403},[1397,10001,9886],{"class":1561},[1397,10003,2572],{"class":1403},[1397,10005,10006],{"class":1399,"line":2753},[1397,10007,10008],{"class":1403},"            }\n",[1397,10010,10011],{"class":1399,"line":2758},[1397,10012,3427],{"class":1403},[1397,10014,10015],{"class":1399,"line":2778},[1397,10016,2023],{"class":1403},[1397,10018,10019],{"class":1399,"line":2783},[1397,10020,1690],{"class":1403},[211,10022,10023],{},"Back in the ContentView - we have a function that receives the data and can perform the decoding etc there.",[8206,10025],{},[1254,10027,10029],{"id":10028},"persisting-the-data","Persisting the data",[211,10031,10032],{},"So - right now - the ContentView can take scanned data and can convert it to a Config option. We want to do three things:",[444,10034,10035,10038,10041],{},[447,10036,10037],{},"Add a save function",[447,10039,10040],{},"Add a load function",[447,10042,10043],{},"Move the decoder out of the view",[211,10045,10046],{},"For simplicity I'm going to add these as static methods on an extension on the Config object itself.",[1698,10048,10050],{"id":10049},"decoder","Decoder",[211,10052,10053],{},"First up we'll move the decode:",[1317,10055,10057],{"className":8610,"code":10056,"language":8332,"meta":227,"style":227},"extension Config {\n    static func decodeConfig(json: String) -> Config? {\n        if let data = json.data(using: .utf8) {\n            let decoder = JSONDecoder()\n\n            if let config = try? decoder.decode(Config.self, from: data) {\n                return config\n            } else {\n                print(\"Could not decode \\(json)\")\n            }\n        } else {\n            print(\"Could not convert to data \\(json)\")\n        }\n\n        return nil\n    }\n}\n",[1296,10058,10059,10068,10099,10125,10138,10142,10169,10177,10187,10204,10208,10217,10233,10237,10241,10249,10253],{"__ignoreMap":227},[1397,10060,10061,10064,10066],{"class":1399,"line":1400},[1397,10062,10063],{"class":1407},"extension",[1397,10065,3749],{"class":1414},[1397,10067,2493],{"class":1403},[1397,10069,10070,10073,10076,10079,10081,10083,10085,10087,10089,10092,10094,10097],{"class":1399,"line":228},[1397,10071,10072],{"class":1407},"    static",[1397,10074,10075],{"class":1407}," func",[1397,10077,10078],{"class":1414}," decodeConfig",[1397,10080,1435],{"class":1403},[1397,10082,8544],{"class":1414},[1397,10084,2513],{"class":1403},[1397,10086,1520],{"class":1561},[1397,10088,3991],{"class":1403},[1397,10090,10091],{"class":1407},"->",[1397,10093,3749],{"class":1403},[1397,10095,10096],{"class":1407},"?",[1397,10098,2493],{"class":1403},[1397,10100,10101,10104,10106,10108,10110,10113,10115,10117,10119,10121,10123],{"class":1399,"line":1426},[1397,10102,10103],{"class":1407},"        if",[1397,10105,9153],{"class":1407},[1397,10107,9156],{"class":1403},[1397,10109,1408],{"class":1407},[1397,10111,10112],{"class":1403}," json.",[1397,10114,9164],{"class":1561},[1397,10116,1435],{"class":1403},[1397,10118,9169],{"class":1561},[1397,10120,9172],{"class":1403},[1397,10122,9175],{"class":1561},[1397,10124,1580],{"class":1403},[1397,10126,10127,10130,10132,10134,10136],{"class":1399,"line":1451},[1397,10128,10129],{"class":1407},"            let",[1397,10131,9184],{"class":1403},[1397,10133,1408],{"class":1407},[1397,10135,9189],{"class":1561},[1397,10137,2572],{"class":1403},[1397,10139,10140],{"class":1399,"line":1470},[1397,10141,1423],{"emptyLinePlaceholder":234},[1397,10143,10144,10147,10149,10151,10153,10155,10157,10159,10161,10163,10165,10167],{"class":1399,"line":1489},[1397,10145,10146],{"class":1407},"            if",[1397,10148,9153],{"class":1407},[1397,10150,9205],{"class":1403},[1397,10152,1408],{"class":1407},[1397,10154,9210],{"class":1407},[1397,10156,9213],{"class":1403},[1397,10158,9216],{"class":1561},[1397,10160,9219],{"class":1403},[1397,10162,9222],{"class":1407},[1397,10164,1442],{"class":1403},[1397,10166,9227],{"class":1561},[1397,10168,9230],{"class":1403},[1397,10170,10171,10174],{"class":1399,"line":1651},[1397,10172,10173],{"class":1407},"                return",[1397,10175,10176],{"class":1403}," config\n",[1397,10178,10179,10182,10185],{"class":1399,"line":1675},[1397,10180,10181],{"class":1403},"            } ",[1397,10183,10184],{"class":1407},"else",[1397,10186,2493],{"class":1403},[1397,10188,10189,10192,10194,10197,10200,10202],{"class":1399,"line":1687},[1397,10190,10191],{"class":1561},"                print",[1397,10193,1435],{"class":1403},[1397,10195,10196],{"class":1438},"\"Could not decode ",[1397,10198,10199],{"class":1438},"\\(json)",[1397,10201,3420],{"class":1438},[1397,10203,2549],{"class":1403},[1397,10205,10206],{"class":1399,"line":2275},[1397,10207,10008],{"class":1403},[1397,10209,10210,10213,10215],{"class":1399,"line":2281},[1397,10211,10212],{"class":1403},"        } ",[1397,10214,10184],{"class":1407},[1397,10216,2493],{"class":1403},[1397,10218,10219,10222,10224,10227,10229,10231],{"class":1399,"line":2540},[1397,10220,10221],{"class":1561},"            print",[1397,10223,1435],{"class":1403},[1397,10225,10226],{"class":1438},"\"Could not convert to data ",[1397,10228,10199],{"class":1438},[1397,10230,3420],{"class":1438},[1397,10232,2549],{"class":1403},[1397,10234,10235],{"class":1399,"line":2552},[1397,10236,3427],{"class":1403},[1397,10238,10239],{"class":1399,"line":2557},[1397,10240,1423],{"emptyLinePlaceholder":234},[1397,10242,10243,10246],{"class":1399,"line":2575},[1397,10244,10245],{"class":1407},"        return",[1397,10247,10248],{"class":1561}," nil\n",[1397,10250,10251],{"class":1399,"line":2580},[1397,10252,2023],{"class":1403},[1397,10254,10255],{"class":1399,"line":2594},[1397,10256,1690],{"class":1403},[1698,10258,10260],{"id":10259},"persisting","Persisting",[211,10262,10263,10264,1133],{},"Next step - save and load. We want to use the Keychain to save this information. The API for the keychain isn't the most swift like - but there are several nice libraries available to make it easier to use. We'll grab ",[1118,10265,10268],{"href":10266,"rel":10267,"target":1125},"https:\u002F\u002Fgithub.com\u002Fevgenyneu\u002Fkeychain-swift.git",[1122,1123,1124],"KeychainSwift",[211,10270,10271,10272,10275],{},"So - add the package ",[1118,10273,10266],{"href":10266,"rel":10274,"target":1125},[1122,1123,1124]," via the Swift Package Manager.",[211,10277,10278],{},"We will need to save the data in some format. We could just store it as Data - but - if we actually encode it to json then in the load function we can simply reuse our decodeConfig to get it back as an object.",[1317,10280,10282],{"className":8610,"code":10281,"language":8332,"meta":227,"style":227},"func save() {\n    let keychain = KeychainSwift()\n\n    let encoder = JSONEncoder()\n\n    if let data = try? encoder.encode(self) {\n        if let json = String(data: data, encoding: .utf8) {\n            keychain.set(json, forKey: \"AppConfig\")\n        }\n    }\n}\n\nstatic func loadConfig() -> Config? {\n    let keychain = KeychainSwift()\n\n    if let json = keychain.get(\"AppConfig\") {\n        return decodeConfig(json: json)\n    }\n\n    return nil\n}\n",[1296,10283,10284,10294,10308,10312,10326,10330,10354,10384,10405,10409,10413,10417,10421,10442,10454,10458,10480,10493,10497,10501,10508],{"__ignoreMap":227},[1397,10285,10286,10288,10291],{"class":1399,"line":1400},[1397,10287,9667],{"class":1407},[1397,10289,10290],{"class":1414}," save",[1397,10292,10293],{"class":1403},"() {\n",[1397,10295,10296,10298,10301,10303,10306],{"class":1399,"line":228},[1397,10297,8633],{"class":1407},[1397,10299,10300],{"class":1403}," keychain ",[1397,10302,1408],{"class":1407},[1397,10304,10305],{"class":1561}," KeychainSwift",[1397,10307,2572],{"class":1403},[1397,10309,10310],{"class":1399,"line":1426},[1397,10311,1423],{"emptyLinePlaceholder":234},[1397,10313,10314,10316,10319,10321,10324],{"class":1399,"line":1451},[1397,10315,8633],{"class":1407},[1397,10317,10318],{"class":1403}," encoder ",[1397,10320,1408],{"class":1407},[1397,10322,10323],{"class":1561}," JSONEncoder",[1397,10325,2572],{"class":1403},[1397,10327,10328],{"class":1399,"line":1470},[1397,10329,1423],{"emptyLinePlaceholder":234},[1397,10331,10332,10334,10336,10338,10340,10342,10345,10348,10350,10352],{"class":1399,"line":1489},[1397,10333,9200],{"class":1407},[1397,10335,9153],{"class":1407},[1397,10337,9156],{"class":1403},[1397,10339,1408],{"class":1407},[1397,10341,9210],{"class":1407},[1397,10343,10344],{"class":1403}," encoder.",[1397,10346,10347],{"class":1561},"encode",[1397,10349,1435],{"class":1403},[1397,10351,9222],{"class":1561},[1397,10353,1580],{"class":1403},[1397,10355,10356,10358,10360,10363,10365,10368,10370,10372,10375,10378,10380,10382],{"class":1399,"line":1651},[1397,10357,10103],{"class":1407},[1397,10359,9153],{"class":1407},[1397,10361,10362],{"class":1403}," json ",[1397,10364,1408],{"class":1407},[1397,10366,10367],{"class":1561}," String",[1397,10369,1435],{"class":1403},[1397,10371,9164],{"class":1561},[1397,10373,10374],{"class":1403},": data, ",[1397,10376,10377],{"class":1561},"encoding",[1397,10379,9172],{"class":1403},[1397,10381,9175],{"class":1561},[1397,10383,1580],{"class":1403},[1397,10385,10386,10389,10392,10395,10398,10400,10403],{"class":1399,"line":1675},[1397,10387,10388],{"class":1403},"            keychain.",[1397,10390,10391],{"class":1561},"set",[1397,10393,10394],{"class":1403},"(json, ",[1397,10396,10397],{"class":1561},"forKey",[1397,10399,2513],{"class":1403},[1397,10401,10402],{"class":1438},"\"AppConfig\"",[1397,10404,2549],{"class":1403},[1397,10406,10407],{"class":1399,"line":1687},[1397,10408,3427],{"class":1403},[1397,10410,10411],{"class":1399,"line":2275},[1397,10412,2023],{"class":1403},[1397,10414,10415],{"class":1399,"line":2281},[1397,10416,1690],{"class":1403},[1397,10418,10419],{"class":1399,"line":2540},[1397,10420,1423],{"emptyLinePlaceholder":234},[1397,10422,10423,10426,10428,10431,10434,10436,10438,10440],{"class":1399,"line":2552},[1397,10424,10425],{"class":1407},"static",[1397,10427,10075],{"class":1407},[1397,10429,10430],{"class":1414}," loadConfig",[1397,10432,10433],{"class":1403},"() ",[1397,10435,10091],{"class":1407},[1397,10437,3749],{"class":1403},[1397,10439,10096],{"class":1407},[1397,10441,2493],{"class":1403},[1397,10443,10444,10446,10448,10450,10452],{"class":1399,"line":2557},[1397,10445,8633],{"class":1407},[1397,10447,10300],{"class":1403},[1397,10449,1408],{"class":1407},[1397,10451,10305],{"class":1561},[1397,10453,2572],{"class":1403},[1397,10455,10456],{"class":1399,"line":2575},[1397,10457,1423],{"emptyLinePlaceholder":234},[1397,10459,10460,10462,10464,10466,10468,10471,10474,10476,10478],{"class":1399,"line":2580},[1397,10461,9200],{"class":1407},[1397,10463,9153],{"class":1407},[1397,10465,10362],{"class":1403},[1397,10467,1408],{"class":1407},[1397,10469,10470],{"class":1403}," keychain.",[1397,10472,10473],{"class":1561},"get",[1397,10475,1435],{"class":1403},[1397,10477,10402],{"class":1438},[1397,10479,1580],{"class":1403},[1397,10481,10482,10484,10486,10488,10490],{"class":1399,"line":2594},[1397,10483,10245],{"class":1407},[1397,10485,10078],{"class":1561},[1397,10487,1435],{"class":1403},[1397,10489,8544],{"class":1561},[1397,10491,10492],{"class":1403},": json)\n",[1397,10494,10495],{"class":1399,"line":2607},[1397,10496,2023],{"class":1403},[1397,10498,10499],{"class":1399,"line":2627},[1397,10500,1423],{"emptyLinePlaceholder":234},[1397,10502,10503,10506],{"class":1399,"line":2644},[1397,10504,10505],{"class":1407},"    return",[1397,10507,10248],{"class":1561},[1397,10509,10510],{"class":1399,"line":2649},[1397,10511,1690],{"class":1403},[211,10513,10514],{},"So now we cansave our new config on fetch and load it for use if present on start. For now - we'll handle this as a simple state variable in ContentView:",[1698,10516,10518],{"id":10517},"fetch-and-save","Fetch and save",[1317,10520,10522],{"className":8610,"code":10521,"language":8332,"meta":227,"style":227},"@State private var config : Config? = nil\n",[1296,10523,10524],{"__ignoreMap":227},[1397,10525,10526,10528,10530,10532,10535,10537,10539],{"class":1399,"line":1400},[1397,10527,8836],{"class":1407},[1397,10529,8839],{"class":1407},[1397,10531,8842],{"class":1407},[1397,10533,10534],{"class":1403}," config : Config",[1397,10536,10096],{"class":1407},[1397,10538,2215],{"class":1407},[1397,10540,10248],{"class":1561},[211,10542,10543],{},"Then our fetch function becomes:",[1317,10545,10547],{"className":8610,"code":10546,"language":8332,"meta":227,"style":227},"func newScanData(_ code: String) {\n    if let config = Config.decodeConfig(json: code) {\n        config.save()\n        self.config = config\n    }\n}\n",[1296,10548,10549,10565,10588,10598,10610,10614],{"__ignoreMap":227},[1397,10550,10551,10553,10555,10557,10559,10561,10563],{"class":1399,"line":1400},[1397,10552,9667],{"class":1407},[1397,10554,9670],{"class":1414},[1397,10556,1435],{"class":1403},[1397,10558,9675],{"class":1414},[1397,10560,9678],{"class":1403},[1397,10562,1520],{"class":1561},[1397,10564,1580],{"class":1403},[1397,10566,10567,10569,10571,10573,10575,10578,10581,10583,10585],{"class":1399,"line":228},[1397,10568,9200],{"class":1407},[1397,10570,9153],{"class":1407},[1397,10572,9205],{"class":1403},[1397,10574,1408],{"class":1407},[1397,10576,10577],{"class":1403}," Config.",[1397,10579,10580],{"class":1561},"decodeConfig",[1397,10582,1435],{"class":1403},[1397,10584,8544],{"class":1561},[1397,10586,10587],{"class":1403},": code) {\n",[1397,10589,10590,10593,10596],{"class":1399,"line":1426},[1397,10591,10592],{"class":1403},"        config.",[1397,10594,10595],{"class":1561},"save",[1397,10597,2572],{"class":1403},[1397,10599,10600,10603,10606,10608],{"class":1399,"line":1451},[1397,10601,10602],{"class":1561},"        self",[1397,10604,10605],{"class":1403},".config ",[1397,10607,1408],{"class":1407},[1397,10609,10176],{"class":1403},[1397,10611,10612],{"class":1399,"line":1470},[1397,10613,2023],{"class":1403},[1397,10615,10616],{"class":1399,"line":1489},[1397,10617,1690],{"class":1403},[1698,10619,10621],{"id":10620},"load-and-display","Load and display",[211,10623,10624],{},"To make it easier to see what we have - we'll add a Text showing the account number if present - and we'll use onAppear to load from config if its there.",[211,10626,10627],{},"This becomes the current view:",[1317,10629,10631],{"className":8610,"code":10630,"language":8332,"meta":227,"style":227},"var body: some View {\n    VStack {\n        Button(action: {\n            self.showingScanner = true\n        }) {\n            Image(systemName: \"qrcode\")\n                .resizable()\n                .frame(width: 32, height: 32)\n        }\n        .sheet(isPresented: $showingScanner) {\n            ScannerView(scannedData: Binding(\n            get: { \"\" },\n            set: self.newScanData\n            ))\n        }\n\n        if (self.config != nil) {\n            Text(config!.accountNr)\n        } else {\n            Text(\"You need to scan in a configuation\")\n        }\n    }\n    .onAppear {\n        self.config = Config.loadConfig()\n    }\n}\n",[1296,10632,10633,10643,10650,10661,10672,10677,10692,10701,10725,10729,10741,10756,10767,10778,10783,10787,10791,10809,10823,10831,10842,10846,10850,10860,10875,10879],{"__ignoreMap":227},[1397,10634,10635,10637,10639,10641],{"class":1399,"line":1400},[1397,10636,9569],{"class":1407},[1397,10638,9755],{"class":1403},[1397,10640,9758],{"class":1407},[1397,10642,9761],{"class":1403},[1397,10644,10645,10648],{"class":1399,"line":228},[1397,10646,10647],{"class":1561},"    VStack",[1397,10649,2493],{"class":1403},[1397,10651,10652,10655,10657,10659],{"class":1399,"line":1426},[1397,10653,10654],{"class":1561},"        Button",[1397,10656,1435],{"class":1403},[1397,10658,8875],{"class":1561},[1397,10660,8878],{"class":1403},[1397,10662,10663,10666,10668,10670],{"class":1399,"line":1451},[1397,10664,10665],{"class":1561},"            self",[1397,10667,8886],{"class":1403},[1397,10669,1408],{"class":1407},[1397,10671,8891],{"class":1561},[1397,10673,10674],{"class":1399,"line":1470},[1397,10675,10676],{"class":1403},"        }) {\n",[1397,10678,10679,10682,10684,10686,10688,10690],{"class":1399,"line":1489},[1397,10680,10681],{"class":1561},"            Image",[1397,10683,1435],{"class":1403},[1397,10685,8906],{"class":1561},[1397,10687,2513],{"class":1403},[1397,10689,8911],{"class":1438},[1397,10691,2549],{"class":1403},[1397,10693,10694,10697,10699],{"class":1399,"line":1651},[1397,10695,10696],{"class":1403},"                .",[1397,10698,8921],{"class":1561},[1397,10700,2572],{"class":1403},[1397,10702,10703,10705,10707,10709,10711,10713,10715,10717,10719,10721,10723],{"class":1399,"line":1675},[1397,10704,10696],{"class":1403},[1397,10706,8930],{"class":1561},[1397,10708,1435],{"class":1403},[1397,10710,8935],{"class":1561},[1397,10712,2513],{"class":1403},[1397,10714,8940],{"class":1561},[1397,10716,1442],{"class":1403},[1397,10718,8945],{"class":1561},[1397,10720,2513],{"class":1403},[1397,10722,8940],{"class":1561},[1397,10724,2549],{"class":1403},[1397,10726,10727],{"class":1399,"line":1687},[1397,10728,3427],{"class":1403},[1397,10730,10731,10733,10735,10737,10739],{"class":1399,"line":2275},[1397,10732,8918],{"class":1403},[1397,10734,8970],{"class":1561},[1397,10736,1435],{"class":1403},[1397,10738,8975],{"class":1561},[1397,10740,8978],{"class":1403},[1397,10742,10743,10746,10748,10750,10752,10754],{"class":1399,"line":2281},[1397,10744,10745],{"class":1561},"            ScannerView",[1397,10747,1435],{"class":1403},[1397,10749,9613],{"class":1561},[1397,10751,2513],{"class":1403},[1397,10753,9618],{"class":1561},[1397,10755,2256],{"class":1403},[1397,10757,10758,10761,10763,10765],{"class":1399,"line":2540},[1397,10759,10760],{"class":1561},"            get",[1397,10762,9628],{"class":1403},[1397,10764,9631],{"class":1438},[1397,10766,9634],{"class":1403},[1397,10768,10769,10772,10774,10776],{"class":1399,"line":2552},[1397,10770,10771],{"class":1561},"            set",[1397,10773,2513],{"class":1403},[1397,10775,9222],{"class":1561},[1397,10777,9646],{"class":1403},[1397,10779,10780],{"class":1399,"line":2557},[1397,10781,10782],{"class":1403},"            ))\n",[1397,10784,10785],{"class":1399,"line":2575},[1397,10786,3427],{"class":1403},[1397,10788,10789],{"class":1399,"line":2580},[1397,10790,1423],{"emptyLinePlaceholder":234},[1397,10792,10793,10795,10797,10799,10801,10804,10807],{"class":1399,"line":2594},[1397,10794,10103],{"class":1407},[1397,10796,1550],{"class":1403},[1397,10798,9222],{"class":1561},[1397,10800,10605],{"class":1403},[1397,10802,10803],{"class":1407},"!=",[1397,10805,10806],{"class":1561}," nil",[1397,10808,1580],{"class":1403},[1397,10810,10811,10814,10817,10820],{"class":1399,"line":2607},[1397,10812,10813],{"class":1561},"            Text",[1397,10815,10816],{"class":1403},"(config",[1397,10818,10819],{"class":1407},"!",[1397,10821,10822],{"class":1403},".accountNr)\n",[1397,10824,10825,10827,10829],{"class":1399,"line":2627},[1397,10826,10212],{"class":1403},[1397,10828,10184],{"class":1407},[1397,10830,2493],{"class":1403},[1397,10832,10833,10835,10837,10840],{"class":1399,"line":2644},[1397,10834,10813],{"class":1561},[1397,10836,1435],{"class":1403},[1397,10838,10839],{"class":1438},"\"You need to scan in a configuation\"",[1397,10841,2549],{"class":1403},[1397,10843,10844],{"class":1399,"line":2649},[1397,10845,3427],{"class":1403},[1397,10847,10848],{"class":1399,"line":2675},[1397,10849,2023],{"class":1403},[1397,10851,10852,10855,10858],{"class":1399,"line":2680},[1397,10853,10854],{"class":1403},"    .",[1397,10856,10857],{"class":1561},"onAppear",[1397,10859,2493],{"class":1403},[1397,10861,10862,10864,10866,10868,10870,10873],{"class":1399,"line":2693},[1397,10863,10602],{"class":1561},[1397,10865,10605],{"class":1403},[1397,10867,1408],{"class":1407},[1397,10869,10577],{"class":1403},[1397,10871,10872],{"class":1561},"loadConfig",[1397,10874,2572],{"class":1403},[1397,10876,10877],{"class":1399,"line":2698},[1397,10878,2023],{"class":1403},[1397,10880,10881],{"class":1399,"line":2719},[1397,10882,1690],{"class":1403},[211,10884,10885],{},"SwiftUI doesn't like things like \"if let x ...\" but will happily cope with a simple if\u002Felse with a check on nil.",[8206,10887],{},[1254,10889,2061],{"id":2060},[211,10891,10892],{},"So - we can now save the configuration to the device keychain on a scan and load it from the keychain at startup. The next step will be to protect the app with Face or Touch ID.",[8206,10894],{},[211,10896,10897],{},[1118,10898,8315],{"href":8313,"rel":10899,"target":1125},[1122,1123,1124],[2066,10901,10902],{},"html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":227,"searchDepth":228,"depth":228,"links":10904},[10905,10909,10915],{"id":9525,"depth":228,"text":9526,"children":10906},[10907,10908],{"id":9546,"depth":1426,"text":9547},{"id":9582,"depth":1426,"text":9583},{"id":10028,"depth":228,"text":10029,"children":10910},[10911,10912,10913,10914],{"id":10049,"depth":1426,"text":10050},{"id":10259,"depth":1426,"text":10260},{"id":10517,"depth":1426,"text":10518},{"id":10620,"depth":1426,"text":10621},{"id":2060,"depth":228,"text":2061},"2020-03-04 07:54 +0100",{},"\u002F2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain",{"title":9515,"description":9520},{"loc":10918},"2020\u002F03\u002F04\u002Fview-refactor-and-save-to-keychain",[8331,8332,8333,8334],"pKNsiMRKINerNECzHU24RwSLQZMqDzsQWgzv0iUd904",{"id":10925,"title":10926,"body":10927,"category":231,"date":11824,"description":10931,"embedImage":231,"extension":232,"image":231,"intro":11825,"meta":11826,"navigation":234,"path":11827,"seo":11828,"series":8325,"sitemap":11829,"stem":11830,"tags":11831,"__hash__":11832},"content\u002F2020\u002F03\u002F08\u002Fbiometric-authentication.md","Biometric authentication",{"type":208,"value":10928,"toc":11810},[10929,10932,10935,10937,10941,10944,10947,10997,11000,11002,11006,11009,11012,11023,11284,11288,11300,11304,11307,11310,11316,11318,11322,11325,11345,11348,11452,11455,11458,11461,11469,11473,11479,11559,11563,11569,11674,11678,11685,11780,11782,11786,11793,11795,11797,11800,11802,11807],[211,10930,10931],{},"So far we have an ap that can scan its config from a QR code and persist it in the keychain on the device.",[211,10933,10934],{},"Today we'll add support for using biometric unlock (Face ID or Touch ID depending on the device).",[8206,10936],{},[1254,10938,10940],{"id":10939},"simulator-data","Simulator Data",[211,10942,10943],{},"However - first we'll make one super quick change to make testing in the simulator a little easier. We'll return a dummy config json in the config scanner.",[211,10945,10946],{},"In ScannerView - create a constant:",[1317,10948,10950],{"className":8610,"code":10949,"language":8332,"meta":227,"style":227},"let simulatedData = \"\"\"\n{\n    \"clientId\": \"clientId\",\n    \"clientSecret\": \"clientSecret\",\n    \"userId\": \"userId\",\n    \"accountNr\": \"12345678903\"\n}\n\"\"\"\n",[1296,10951,10952,10964,10968,10973,10978,10983,10988,10992],{"__ignoreMap":227},[1397,10953,10954,10956,10959,10961],{"class":1399,"line":1400},[1397,10955,9046],{"class":1407},[1397,10957,10958],{"class":1403}," simulatedData ",[1397,10960,1408],{"class":1407},[1397,10962,10963],{"class":1438}," \"\"\"\n",[1397,10965,10966],{"class":1399,"line":228},[1397,10967,8551],{"class":1438},[1397,10969,10970],{"class":1399,"line":1426},[1397,10971,10972],{"class":1438},"    \"clientId\": \"clientId\",\n",[1397,10974,10975],{"class":1399,"line":1451},[1397,10976,10977],{"class":1438},"    \"clientSecret\": \"clientSecret\",\n",[1397,10979,10980],{"class":1399,"line":1470},[1397,10981,10982],{"class":1438},"    \"userId\": \"userId\",\n",[1397,10984,10985],{"class":1399,"line":1489},[1397,10986,10987],{"class":1438},"    \"accountNr\": \"12345678903\"\n",[1397,10989,10990],{"class":1399,"line":1651},[1397,10991,1690],{"class":1438},[1397,10993,10994],{"class":1399,"line":1675},[1397,10995,10996],{"class":1438},"\"\"\"\n",[211,10998,10999],{},"And then in the call to CodeScannerView pass the simulatedData variable instead of \"-\"",[8206,11001],{},[1254,11003,11005],{"id":11004},"touch-idface-id","Touch ID\u002FFace ID",[211,11007,11008],{},"These use Apple's LocalAuthentication module.",[211,11010,11011],{},"Create a swift file to hold some utility functions - Authentication.swift.",[211,11013,11014,11015,11018,11019,11022],{},"Import ",[1296,11016,11017],{},"LocalAuthentication"," just after ",[1296,11020,11021],{},"Foundation"," then create the following utility method and enum:",[1317,11024,11026],{"className":8610,"code":11025,"language":8332,"meta":227,"style":227},"enum AuthStatus {\n    case OK\n    case Error\n    case Unavailable\n}\n\nfunc authenticateUser(_ callback: @escaping (_ status: AuthStatus) -> Void) {\n    let context = LAContext()\n    var error: NSError?\n\n    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {\n        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: \"Unlock\") { success, authenticationError in\n            DispatchQueue.main.async {\n                if success {\n                    print(\"Authentication OK\")\n                    callback(.OK)\n                } else {\n                    print(\"Authentication Error\")\n                    callback(.Error)\n                }\n            }\n        }\n    } else {\n        print(\"Authentication Unavailable\")\n        callback(.Unavailable)\n    }\n}\n",[1296,11027,11028,11037,11044,11051,11058,11062,11066,11093,11107,11117,11121,11145,11168,11178,11186,11197,11205,11214,11225,11237,11241,11245,11249,11257,11268,11276,11280],{"__ignoreMap":227},[1397,11029,11030,11032,11035],{"class":1399,"line":1400},[1397,11031,8682],{"class":1407},[1397,11033,11034],{"class":1414}," AuthStatus",[1397,11036,2493],{"class":1403},[1397,11038,11039,11041],{"class":1399,"line":228},[1397,11040,3960],{"class":1407},[1397,11042,11043],{"class":1561}," OK\n",[1397,11045,11046,11048],{"class":1399,"line":1426},[1397,11047,3960],{"class":1407},[1397,11049,11050],{"class":1561}," Error\n",[1397,11052,11053,11055],{"class":1399,"line":1451},[1397,11054,3960],{"class":1407},[1397,11056,11057],{"class":1561}," Unavailable\n",[1397,11059,11060],{"class":1399,"line":1470},[1397,11061,1690],{"class":1403},[1397,11063,11064],{"class":1399,"line":1489},[1397,11065,1423],{"emptyLinePlaceholder":234},[1397,11067,11068,11070,11073,11075,11077,11080,11083,11086,11088,11091],{"class":1399,"line":1651},[1397,11069,9667],{"class":1407},[1397,11071,11072],{"class":1414}," authenticateUser",[1397,11074,1435],{"class":1403},[1397,11076,9675],{"class":1414},[1397,11078,11079],{"class":1403}," callback: ",[1397,11081,11082],{"class":1407},"@escaping",[1397,11084,11085],{"class":1403}," (_ status: AuthStatus) ",[1397,11087,10091],{"class":1407},[1397,11089,11090],{"class":1561}," Void",[1397,11092,1580],{"class":1403},[1397,11094,11095,11097,11100,11102,11105],{"class":1399,"line":1675},[1397,11096,8633],{"class":1407},[1397,11098,11099],{"class":1403}," context ",[1397,11101,1408],{"class":1407},[1397,11103,11104],{"class":1561}," LAContext",[1397,11106,2572],{"class":1403},[1397,11108,11109,11111,11114],{"class":1399,"line":1687},[1397,11110,9752],{"class":1407},[1397,11112,11113],{"class":1403}," error: NSError",[1397,11115,11116],{"class":1407},"?\n",[1397,11118,11119],{"class":1399,"line":2275},[1397,11120,1423],{"emptyLinePlaceholder":234},[1397,11122,11123,11125,11128,11131,11134,11137,11139,11142],{"class":1399,"line":2281},[1397,11124,9200],{"class":1407},[1397,11126,11127],{"class":1403}," context.",[1397,11129,11130],{"class":1561},"canEvaluatePolicy",[1397,11132,11133],{"class":1403},"(.deviceOwnerAuthenticationWithBiometrics, ",[1397,11135,11136],{"class":1561},"error",[1397,11138,2513],{"class":1403},[1397,11140,11141],{"class":1407},"&",[1397,11143,11144],{"class":1403},"error) {\n",[1397,11146,11147,11150,11153,11155,11158,11160,11163,11166],{"class":1399,"line":2540},[1397,11148,11149],{"class":1403},"        context.",[1397,11151,11152],{"class":1561},"evaluatePolicy",[1397,11154,11133],{"class":1403},[1397,11156,11157],{"class":1561},"localizedReason",[1397,11159,2513],{"class":1403},[1397,11161,11162],{"class":1438},"\"Unlock\"",[1397,11164,11165],{"class":1403},") { success, authenticationError ",[1397,11167,9022],{"class":1407},[1397,11169,11170,11173,11176],{"class":1399,"line":2552},[1397,11171,11172],{"class":1403},"            DispatchQueue.main.",[1397,11174,11175],{"class":1561},"async",[1397,11177,2493],{"class":1403},[1397,11179,11180,11183],{"class":1399,"line":2557},[1397,11181,11182],{"class":1407},"                if",[1397,11184,11185],{"class":1403}," success {\n",[1397,11187,11188,11190,11192,11195],{"class":1399,"line":2575},[1397,11189,9985],{"class":1561},[1397,11191,1435],{"class":1403},[1397,11193,11194],{"class":1438},"\"Authentication OK\"",[1397,11196,2549],{"class":1403},[1397,11198,11199,11202],{"class":1399,"line":2580},[1397,11200,11201],{"class":1561},"                    callback",[1397,11203,11204],{"class":1403},"(.OK)\n",[1397,11206,11207,11210,11212],{"class":1399,"line":2594},[1397,11208,11209],{"class":1403},"                } ",[1397,11211,10184],{"class":1407},[1397,11213,2493],{"class":1403},[1397,11215,11216,11218,11220,11223],{"class":1399,"line":2607},[1397,11217,9985],{"class":1561},[1397,11219,1435],{"class":1403},[1397,11221,11222],{"class":1438},"\"Authentication Error\"",[1397,11224,2549],{"class":1403},[1397,11226,11227,11229,11232,11235],{"class":1399,"line":2627},[1397,11228,11201],{"class":1561},[1397,11230,11231],{"class":1403},"(.",[1397,11233,11234],{"class":1561},"Error",[1397,11236,2549],{"class":1403},[1397,11238,11239],{"class":1399,"line":2644},[1397,11240,9992],{"class":1403},[1397,11242,11243],{"class":1399,"line":2649},[1397,11244,10008],{"class":1403},[1397,11246,11247],{"class":1399,"line":2675},[1397,11248,3427],{"class":1403},[1397,11250,11251,11253,11255],{"class":1399,"line":2680},[1397,11252,3438],{"class":1403},[1397,11254,10184],{"class":1407},[1397,11256,2493],{"class":1403},[1397,11258,11259,11261,11263,11266],{"class":1399,"line":2693},[1397,11260,9235],{"class":1561},[1397,11262,1435],{"class":1403},[1397,11264,11265],{"class":1438},"\"Authentication Unavailable\"",[1397,11267,2549],{"class":1403},[1397,11269,11270,11273],{"class":1399,"line":2698},[1397,11271,11272],{"class":1561},"        callback",[1397,11274,11275],{"class":1403},"(.Unavailable)\n",[1397,11277,11278],{"class":1399,"line":2719},[1397,11279,2023],{"class":1403},[1397,11281,11282],{"class":1399,"line":2737},[1397,11283,1690],{"class":1403},[1698,11285,11287],{"id":11286},"notes","Notes",[444,11289,11290,11297],{},[447,11291,11292,11293,11296],{},"We're calling an older Objective-C api here - things like ",[1296,11294,11295],{},"error: &error"," don't really feel so \"swift\" but they still work.",[447,11298,11299],{},"The call to evaluatePolicy happens in a different thread - we need to switch back to our main thread to handle the result",[1698,11301,11303],{"id":11302},"face-id","Face ID",[211,11305,11306],{},"But wait - this will only work for Touch ID. We gave a (too brief) reason to the call to evaluatePolicy here - but that is only used for Touch ID. For Face ID we have to add it to the Info.plist instead.",[211,11308,11309],{},"Open Info.plist and add a new line:",[1317,11311,11314],{"className":11312,"code":11313,"language":1322},[1320],"\"Privacy - Face ID Usage Description\" - \"Unlock\"\n",[1296,11315,11313],{"__ignoreMap":227},[8206,11317],{},[1254,11319,11321],{"id":11320},"using-the-authenticate-function-in-the-view","Using the authenticate function in the view",[211,11323,11324],{},"Add the following state variable:",[1317,11326,11328],{"className":8610,"code":11327,"language":8332,"meta":227,"style":227},"@State private var authenticated = false\n",[1296,11329,11330],{"__ignoreMap":227},[1397,11331,11332,11334,11336,11338,11341,11343],{"class":1399,"line":1400},[1397,11333,8836],{"class":1407},[1397,11335,8839],{"class":1407},[1397,11337,8842],{"class":1407},[1397,11339,11340],{"class":1403}," authenticated ",[1397,11342,1408],{"class":1407},[1397,11344,8850],{"class":1561},[211,11346,11347],{},"A quick utility function to call and handle the response:",[1317,11349,11351],{"className":8610,"code":11350,"language":8332,"meta":227,"style":227},"func askForAuth() {\n    authenticateUser() { status in\n        switch(status) {\n        case .OK:\n            self.authenticated = true\n        case .Error:\n            self.authenticated = false\n        case .Unavailable:\n            self.authenticated = true\n        }\n    }\n}\n",[1296,11352,11353,11362,11372,11380,11390,11401,11411,11421,11430,11440,11444,11448],{"__ignoreMap":227},[1397,11354,11355,11357,11360],{"class":1399,"line":1400},[1397,11356,9667],{"class":1407},[1397,11358,11359],{"class":1414}," askForAuth",[1397,11361,10293],{"class":1403},[1397,11363,11364,11367,11370],{"class":1399,"line":228},[1397,11365,11366],{"class":1561},"    authenticateUser",[1397,11368,11369],{"class":1403},"() { status ",[1397,11371,9022],{"class":1407},[1397,11373,11374,11377],{"class":1399,"line":1426},[1397,11375,11376],{"class":1407},"        switch",[1397,11378,11379],{"class":1403},"(status) {\n",[1397,11381,11382,11385,11388],{"class":1399,"line":1451},[1397,11383,11384],{"class":1407},"        case",[1397,11386,11387],{"class":1403}," .OK",[1397,11389,9052],{"class":1407},[1397,11391,11392,11394,11397,11399],{"class":1399,"line":1470},[1397,11393,10665],{"class":1561},[1397,11395,11396],{"class":1403},".authenticated ",[1397,11398,1408],{"class":1407},[1397,11400,8891],{"class":1561},[1397,11402,11403,11405,11407,11409],{"class":1399,"line":1489},[1397,11404,11384],{"class":1407},[1397,11406,9038],{"class":1403},[1397,11408,11234],{"class":1561},[1397,11410,9052],{"class":1407},[1397,11412,11413,11415,11417,11419],{"class":1399,"line":1651},[1397,11414,10665],{"class":1561},[1397,11416,11396],{"class":1403},[1397,11418,1408],{"class":1407},[1397,11420,8850],{"class":1561},[1397,11422,11423,11425,11428],{"class":1399,"line":1675},[1397,11424,11384],{"class":1407},[1397,11426,11427],{"class":1403}," .Unavailable",[1397,11429,9052],{"class":1407},[1397,11431,11432,11434,11436,11438],{"class":1399,"line":1687},[1397,11433,10665],{"class":1561},[1397,11435,11396],{"class":1403},[1397,11437,1408],{"class":1407},[1397,11439,8891],{"class":1561},[1397,11441,11442],{"class":1399,"line":2275},[1397,11443,3427],{"class":1403},[1397,11445,11446],{"class":1399,"line":2281},[1397,11447,2023],{"class":1403},[1397,11449,11450],{"class":1399,"line":2540},[1397,11451,1690],{"class":1403},[211,11453,11454],{},"Note that if authentication is unavailable we're just letting the user in. Here you would need fallback to a login or similar - but for this proof of concept app (and given that my son has a touch ID device) this will do here.",[211,11456,11457],{},"So - when do we call this?",[211,11459,11460],{},"I want to call this in two places:",[444,11462,11463,11466],{},[447,11464,11465],{},"When the app starts - if we already have configuration",[447,11467,11468],{},"When a new config successfully is scanned",[1698,11470,11472],{"id":11471},"app-load","App Load",[211,11474,11475,11476,1981],{},"Add the if clause after the call to ",[1296,11477,11478],{},"loadConfig()",[1317,11480,11482],{"className":8610,"code":11481,"language":8332,"meta":227,"style":227},".onAppear {\n    self.config = Config.loadConfig()\n\n    if (self.config != nil && self.authenticated == false) {\n        self.askForAuth()\n    }\n}\n",[1296,11483,11484,11492,11506,11510,11540,11551,11555],{"__ignoreMap":227},[1397,11485,11486,11488,11490],{"class":1399,"line":1400},[1397,11487,1133],{"class":1403},[1397,11489,10857],{"class":1561},[1397,11491,2493],{"class":1403},[1397,11493,11494,11496,11498,11500,11502,11504],{"class":1399,"line":228},[1397,11495,8883],{"class":1561},[1397,11497,10605],{"class":1403},[1397,11499,1408],{"class":1407},[1397,11501,10577],{"class":1403},[1397,11503,10872],{"class":1561},[1397,11505,2572],{"class":1403},[1397,11507,11508],{"class":1399,"line":1426},[1397,11509,1423],{"emptyLinePlaceholder":234},[1397,11511,11512,11514,11516,11518,11520,11522,11524,11527,11530,11532,11535,11538],{"class":1399,"line":1451},[1397,11513,9200],{"class":1407},[1397,11515,1550],{"class":1403},[1397,11517,9222],{"class":1561},[1397,11519,10605],{"class":1403},[1397,11521,10803],{"class":1407},[1397,11523,10806],{"class":1561},[1397,11525,11526],{"class":1407}," &&",[1397,11528,11529],{"class":1561}," self",[1397,11531,11396],{"class":1403},[1397,11533,11534],{"class":1407},"==",[1397,11536,11537],{"class":1561}," false",[1397,11539,1580],{"class":1403},[1397,11541,11542,11544,11546,11549],{"class":1399,"line":1470},[1397,11543,10602],{"class":1561},[1397,11545,1133],{"class":1403},[1397,11547,11548],{"class":1561},"askForAuth",[1397,11550,2572],{"class":1403},[1397,11552,11553],{"class":1399,"line":1489},[1397,11554,2023],{"class":1403},[1397,11556,11557],{"class":1399,"line":1651},[1397,11558,1690],{"class":1403},[1698,11560,11562],{"id":11561},"config-load","Config load",[211,11564,11565,11566,1981],{},"In newScanData - add the if clause after the line ",[1296,11567,11568],{},"self.config = config",[1317,11570,11572],{"className":8610,"code":11571,"language":8332,"meta":227,"style":227}," func newScanData(_ code: String) {\n     if let config = Config.decodeConfig(json: code) {\n         config.save()\n         self.config = config\n\n         if (self.authenticated == false) {\n             self.askForAuth()\n         }\n     }\n }\n",[1296,11573,11574,11590,11611,11620,11631,11635,11651,11662,11666,11670],{"__ignoreMap":227},[1397,11575,11576,11578,11580,11582,11584,11586,11588],{"class":1399,"line":1400},[1397,11577,10075],{"class":1407},[1397,11579,9670],{"class":1414},[1397,11581,1435],{"class":1403},[1397,11583,9675],{"class":1414},[1397,11585,9678],{"class":1403},[1397,11587,1520],{"class":1561},[1397,11589,1580],{"class":1403},[1397,11591,11592,11595,11597,11599,11601,11603,11605,11607,11609],{"class":1399,"line":228},[1397,11593,11594],{"class":1407},"     if",[1397,11596,9153],{"class":1407},[1397,11598,9205],{"class":1403},[1397,11600,1408],{"class":1407},[1397,11602,10577],{"class":1403},[1397,11604,10580],{"class":1561},[1397,11606,1435],{"class":1403},[1397,11608,8544],{"class":1561},[1397,11610,10587],{"class":1403},[1397,11612,11613,11616,11618],{"class":1399,"line":1426},[1397,11614,11615],{"class":1403},"         config.",[1397,11617,10595],{"class":1561},[1397,11619,2572],{"class":1403},[1397,11621,11622,11625,11627,11629],{"class":1399,"line":1451},[1397,11623,11624],{"class":1561},"         self",[1397,11626,10605],{"class":1403},[1397,11628,1408],{"class":1407},[1397,11630,10176],{"class":1403},[1397,11632,11633],{"class":1399,"line":1470},[1397,11634,1423],{"emptyLinePlaceholder":234},[1397,11636,11637,11639,11641,11643,11645,11647,11649],{"class":1399,"line":1489},[1397,11638,9335],{"class":1407},[1397,11640,1550],{"class":1403},[1397,11642,9222],{"class":1561},[1397,11644,11396],{"class":1403},[1397,11646,11534],{"class":1407},[1397,11648,11537],{"class":1561},[1397,11650,1580],{"class":1403},[1397,11652,11653,11656,11658,11660],{"class":1399,"line":1651},[1397,11654,11655],{"class":1561},"             self",[1397,11657,1133],{"class":1403},[1397,11659,11548],{"class":1561},[1397,11661,2572],{"class":1403},[1397,11663,11664],{"class":1399,"line":1675},[1397,11665,9434],{"class":1403},[1397,11667,11668],{"class":1399,"line":1687},[1397,11669,9090],{"class":1403},[1397,11671,11672],{"class":1399,"line":2275},[1397,11673,9106],{"class":1403},[1698,11675,11677],{"id":11676},"view","View",[211,11679,11680,11681,11684],{},"In the view - we currently have a conditional display of the account number or the string \"You need to scan in a configuation\". Extend this to check the ",[1296,11682,11683],{},"authenticated"," state:",[1317,11686,11688],{"className":8610,"code":11687,"language":8332,"meta":227,"style":227},"if (self.config != nil) {\n    if (self.authenticated == false) {\n        Text(\"Please authenticate\")\n    } else {\n        Text(config!.accountNr)\n    }\n} else {\n    Text(\"You need to scan in a configuation\")\n}\n",[1296,11689,11690,11706,11722,11734,11742,11752,11756,11765,11776],{"__ignoreMap":227},[1397,11691,11692,11694,11696,11698,11700,11702,11704],{"class":1399,"line":1400},[1397,11693,9150],{"class":1407},[1397,11695,1550],{"class":1403},[1397,11697,9222],{"class":1561},[1397,11699,10605],{"class":1403},[1397,11701,10803],{"class":1407},[1397,11703,10806],{"class":1561},[1397,11705,1580],{"class":1403},[1397,11707,11708,11710,11712,11714,11716,11718,11720],{"class":1399,"line":228},[1397,11709,9200],{"class":1407},[1397,11711,1550],{"class":1403},[1397,11713,9222],{"class":1561},[1397,11715,11396],{"class":1403},[1397,11717,11534],{"class":1407},[1397,11719,11537],{"class":1561},[1397,11721,1580],{"class":1403},[1397,11723,11724,11727,11729,11732],{"class":1399,"line":1426},[1397,11725,11726],{"class":1561},"        Text",[1397,11728,1435],{"class":1403},[1397,11730,11731],{"class":1438},"\"Please authenticate\"",[1397,11733,2549],{"class":1403},[1397,11735,11736,11738,11740],{"class":1399,"line":1451},[1397,11737,3438],{"class":1403},[1397,11739,10184],{"class":1407},[1397,11741,2493],{"class":1403},[1397,11743,11744,11746,11748,11750],{"class":1399,"line":1470},[1397,11745,11726],{"class":1561},[1397,11747,10816],{"class":1403},[1397,11749,10819],{"class":1407},[1397,11751,10822],{"class":1403},[1397,11753,11754],{"class":1399,"line":1489},[1397,11755,2023],{"class":1403},[1397,11757,11758,11761,11763],{"class":1399,"line":1651},[1397,11759,11760],{"class":1403},"} ",[1397,11762,10184],{"class":1407},[1397,11764,2493],{"class":1403},[1397,11766,11767,11770,11772,11774],{"class":1399,"line":1675},[1397,11768,11769],{"class":1561},"    Text",[1397,11771,1435],{"class":1403},[1397,11773,10839],{"class":1438},[1397,11775,2549],{"class":1403},[1397,11777,11778],{"class":1399,"line":1687},[1397,11779,1690],{"class":1403},[8206,11781],{},[1254,11783,11785],{"id":11784},"simulator","Simulator",[211,11787,11788,11789,11792],{},"You can test both Face ID and Touch ID in the simulator if you use the menu ",[1296,11790,11791],{},"Hardware > Face\u002FTouch ID"," first to enrol the device and then also to tell the simulator that the face\u002Ffingerprint is good.",[8206,11794],{},[1254,11796,2061],{"id":2060},[211,11798,11799],{},"We've now added basic support for biometric authentication. In the next post we'll actually start calling the S'banken API and then in further posts we will start doing something with the view layout.",[8206,11801],{},[211,11803,11804],{},[1118,11805,8315],{"href":8313,"rel":11806,"target":1125},[1122,1123,1124],[2066,11808,11809],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":227,"searchDepth":228,"depth":228,"links":11811},[11812,11813,11817,11822,11823],{"id":10939,"depth":228,"text":10940},{"id":11004,"depth":228,"text":11005,"children":11814},[11815,11816],{"id":11286,"depth":1426,"text":11287},{"id":11302,"depth":1426,"text":11303},{"id":11320,"depth":228,"text":11321,"children":11818},[11819,11820,11821],{"id":11471,"depth":1426,"text":11472},{"id":11561,"depth":1426,"text":11562},{"id":11676,"depth":1426,"text":11677},{"id":11784,"depth":228,"text":11785},{"id":2060,"depth":228,"text":2061},"2020-03-08 21:00 +0100","So far we have an ap that can scan its config from a QR code and persist it in the keychain on the device. Today we'll add support for using biometric unlock (Face ID or Touch ID depending on the device).",{},"\u002F2020\u002F03\u002F08\u002Fbiometric-authentication",{"title":10926,"description":10931},{"loc":11827},"2020\u002F03\u002F08\u002Fbiometric-authentication",[8331,8332,8333,8334],"voq9Cyw1UEN1h-nrf3IoCCXlc_A5zmvAY_lETV6GhKc",{"id":11834,"title":11835,"body":11836,"category":27,"date":12898,"description":11840,"embedImage":231,"extension":232,"image":231,"intro":12899,"meta":12900,"navigation":234,"path":12901,"seo":12902,"series":8325,"sitemap":12903,"stem":12904,"tags":12905,"__hash__":12906},"content\u002F2020\u002F03\u002F18\u002Ffetching-the-token.md","Fetching the token",{"type":208,"value":11837,"toc":12890},[11838,11841,11844,11846,11850,11853,11856,11867,11870,11876,11883,11886,11889,11892,12004,12007,12011,12019,12022,12130,12134,12137,12140,12736,12738,12742,12745,12748,12829,12836,12870,12873,12875,12877,12880,12882,12887],[211,11839,11840],{},"The app so far has the ability to read its configuration and to check that you are who you say you are on devices that support touch or face id.",[211,11842,11843],{},"The next step is to get an access token from the S'banken API.",[8206,11845],{},[1254,11847,11849],{"id":11848},"tokens","Tokens",[211,11851,11852],{},"S'banken is using a standard authentication mechanism - OAuth client credentials.",[211,11854,11855],{},"To make this call we need to send a POST request to the token endpoint with the following headers:",[444,11857,11858,11861,11864],{},[447,11859,11860],{},"Accept: application\u002Fjson",[447,11862,11863],{},"Content-Type: application\u002Fx-www-form-urlencoded",[447,11865,11866],{},"Authorization: BASIC",[211,11868,11869],{},"The body must be",[1317,11871,11874],{"className":11872,"code":11873,"language":1322},[1320],"grant_type=client_credentials\n",[1296,11875,11873],{"__ignoreMap":227},[211,11877,11878,11879,11882],{},"So - that authorization header. This call uses basic auth - which is a base 64 encoded version of ",[1296,11880,11881],{},"username:password",". Here - username is the clientId and password is the clientSecret. But we need to do one more step - url encode each part before we base 64 encode it.",[211,11884,11885],{},"Let's create a little utility class with a static function. It will take a config object and a completion callback (the request will be async).",[211,11887,11888],{},"Add a new file - TokenService.swift. At the top - add an import for Alamofire",[211,11890,11891],{},"We'll start by creating a Codable representation of the response:",[1317,11893,11895],{"className":8610,"code":11894,"language":8332,"meta":227,"style":227},"struct Token : Decodable {\n    let accessToken: String\n    let expiresIn: Int\n    let tokenType: String\n\n    enum CodingKeys: String, CodingKey {\n        case accessToken = \"access_token\"\n        case expiresIn = \"expires_in\"\n        case tokenType = \"token_type\"\n    }\n}\n",[1296,11896,11897,11911,11920,11930,11939,11943,11960,11972,11984,11996,12000],{"__ignoreMap":227},[1397,11898,11899,11901,11904,11906,11909],{"class":1399,"line":1400},[1397,11900,9706],{"class":1407},[1397,11902,11903],{"class":1414}," Token",[1397,11905,8623],{"class":1403},[1397,11907,11908],{"class":1414},"Decodable ",[1397,11910,8551],{"class":1403},[1397,11912,11913,11915,11918],{"class":1399,"line":228},[1397,11914,8633],{"class":1407},[1397,11916,11917],{"class":1403}," accessToken: ",[1397,11919,3883],{"class":1561},[1397,11921,11922,11924,11927],{"class":1399,"line":1426},[1397,11923,8633],{"class":1407},[1397,11925,11926],{"class":1403}," expiresIn: ",[1397,11928,11929],{"class":1561},"Int\n",[1397,11931,11932,11934,11937],{"class":1399,"line":1451},[1397,11933,8633],{"class":1407},[1397,11935,11936],{"class":1403}," tokenType: ",[1397,11938,3883],{"class":1561},[1397,11940,11941],{"class":1399,"line":1470},[1397,11942,1423],{"emptyLinePlaceholder":234},[1397,11944,11945,11948,11950,11952,11954,11956,11958],{"class":1399,"line":1489},[1397,11946,11947],{"class":1407},"    enum",[1397,11949,8685],{"class":1414},[1397,11951,2513],{"class":1403},[1397,11953,1520],{"class":1561},[1397,11955,1442],{"class":1403},[1397,11957,8694],{"class":1414},[1397,11959,8551],{"class":1403},[1397,11961,11962,11964,11967,11969],{"class":1399,"line":1651},[1397,11963,11384],{"class":1407},[1397,11965,11966],{"class":1561}," accessToken",[1397,11968,2215],{"class":1407},[1397,11970,11971],{"class":1438}," \"access_token\"\n",[1397,11973,11974,11976,11979,11981],{"class":1399,"line":1675},[1397,11975,11384],{"class":1407},[1397,11977,11978],{"class":1561}," expiresIn",[1397,11980,2215],{"class":1407},[1397,11982,11983],{"class":1438}," \"expires_in\"\n",[1397,11985,11986,11988,11991,11993],{"class":1399,"line":1687},[1397,11987,11384],{"class":1407},[1397,11989,11990],{"class":1561}," tokenType",[1397,11992,2215],{"class":1407},[1397,11994,11995],{"class":1438}," \"token_type\"\n",[1397,11997,11998],{"class":1399,"line":2275},[1397,11999,2023],{"class":1403},[1397,12001,12002],{"class":1399,"line":2281},[1397,12003,1690],{"class":1403},[211,12005,12006],{},"This will be used when parsing the response from the API.",[1698,12008,12010],{"id":12009},"percent-encoding","Percent Encoding",[211,12012,12013,12014,12018],{},"Now - encoding the username and password fields. Swift strings have a method addingPercentEncoding which will do this for us - given the correct allowed CharacterSet. I found that none of the default available character sets worked as I wanted so I took a peek at S'banken's own swift API implementation: ",[1118,12015,12016],{"href":12016,"rel":12017,"target":1125},"https:\u002F\u002Fgithub.com\u002FSbanken\u002Fsbankenclient-ios\u002Fblob\u002Fmaster\u002FSbankenClient\u002FSbankenClient.swift#L257-L261",[1122,1123,1124]," and found that they are constructing a character set - so - let's do that.",[211,12020,12021],{},"We'll add it as an extension onto String:",[1317,12023,12025],{"className":8610,"code":12024,"language":8332,"meta":227,"style":227},"extension String {\n    public func encodeForAuth() -> String? {\n        let characterSet = NSMutableCharacterSet.alphanumeric()\n        characterSet.addCharacters(in: \"-_.!~*'()\")\n\n        return self.addingPercentEncoding(withAllowedCharacters: characterSet as CharacterSet)\n    }\n}\n",[1296,12026,12027,12035,12055,12073,12093,12097,12122,12126],{"__ignoreMap":227},[1397,12028,12029,12031,12033],{"class":1399,"line":1400},[1397,12030,10063],{"class":1407},[1397,12032,10367],{"class":1561},[1397,12034,2493],{"class":1403},[1397,12036,12037,12040,12042,12045,12047,12049,12051,12053],{"class":1399,"line":228},[1397,12038,12039],{"class":1407},"    public",[1397,12041,10075],{"class":1407},[1397,12043,12044],{"class":1414}," encodeForAuth",[1397,12046,10433],{"class":1403},[1397,12048,10091],{"class":1407},[1397,12050,10367],{"class":1561},[1397,12052,10096],{"class":1407},[1397,12054,2493],{"class":1403},[1397,12056,12057,12060,12063,12065,12068,12071],{"class":1399,"line":1426},[1397,12058,12059],{"class":1407},"        let",[1397,12061,12062],{"class":1403}," characterSet ",[1397,12064,1408],{"class":1407},[1397,12066,12067],{"class":1403}," NSMutableCharacterSet.",[1397,12069,12070],{"class":1561},"alphanumeric",[1397,12072,2572],{"class":1403},[1397,12074,12075,12078,12081,12083,12086,12088,12091],{"class":1399,"line":1451},[1397,12076,12077],{"class":1403},"        characterSet.",[1397,12079,12080],{"class":1561},"addCharacters",[1397,12082,1435],{"class":1403},[1397,12084,12085],{"class":1561},"in",[1397,12087,2513],{"class":1403},[1397,12089,12090],{"class":1438},"\"-_.!~*'()\"",[1397,12092,2549],{"class":1403},[1397,12094,12095],{"class":1399,"line":1470},[1397,12096,1423],{"emptyLinePlaceholder":234},[1397,12098,12099,12101,12103,12105,12108,12110,12113,12116,12119],{"class":1399,"line":1489},[1397,12100,10245],{"class":1407},[1397,12102,11529],{"class":1561},[1397,12104,1133],{"class":1403},[1397,12106,12107],{"class":1561},"addingPercentEncoding",[1397,12109,1435],{"class":1403},[1397,12111,12112],{"class":1561},"withAllowedCharacters",[1397,12114,12115],{"class":1403},": characterSet ",[1397,12117,12118],{"class":1407},"as",[1397,12120,12121],{"class":1403}," CharacterSet)\n",[1397,12123,12124],{"class":1399,"line":1651},[1397,12125,2023],{"class":1403},[1397,12127,12128],{"class":1399,"line":1675},[1397,12129,1690],{"class":1403},[1698,12131,12133],{"id":12132},"request","Request",[211,12135,12136],{},"Finally we can start to construct our request.",[211,12138,12139],{},"We create a TokenService class with a static getToken function:",[1317,12141,12143],{"className":8610,"code":12142,"language":8332,"meta":227,"style":227},"class TokenService {\n    public static func getToken(config: Config, onComplete: @escaping (_ accessToken: String?) -> Void) {\n\n        \u002F\u002F Get username param encoded\n        guard let username = config.clientId.encodeForAuth() else {\n            onComplete(nil)\n\n            return\n        }\n\n        \u002F\u002F Get password param encoded\n        guard let password = config.clientSecret.encodeForAuth() else {\n            onComplete(nil)\n\n            return\n        }\n\n        \u002F\u002F Construct the basic auth string\n        let basicAuth = Data(\"\\(username):\\(password)\".utf8).base64EncodedString()\n\n        \u002F\u002F Headers required\n        let headers: HTTPHeaders = [\n            \"Authorization\": \"Basic \\(basicAuth)\",\n            \"Accept\": \"application\u002Fjson\",\n            \"Content-Type\": \"application\u002Fx-www-form-urlencoded\"\n        ]\n\n        \u002F\u002F Body params\n        let parameters = [\"grant_type\": \"client_credentials\"]\n\n        \u002F\u002F Construct the request - setting the body to x-www-form-urlencoded\n        let request = AF.request(\"https:\u002F\u002Fauth.sbanken.no\u002Fidentityserver\u002Fconnect\u002Ftoken\",\n                                 method: .post,\n                                 parameters: parameters,\n                                 encoding: URLEncoding.httpBody,\n                                 headers: headers)\n\n        let decoder = JSONDecoder()\n\n        \u002F\u002F Call the API\n        request.responseDecodable(of: Token.self, decoder: decoder) { (response) in\n            if let error = response.error {\n                \u002F\u002F It went wrong\n                print(\"Unable to fetch token \\(response) \\(error.localizedDescription)\")\n\n                onComplete(nil)\n\n                return\n            }\n\n            guard let token = response.value else {\n                \u002F\u002F We got an answer but could not parse it\n                print(\"Unable to read token\")\n\n                onComplete(nil)\n\n                return\n            }\n\n            \u002F\u002F Token received\n            onComplete(token.accessToken)\n        }\n    }\n}\n",[1296,12144,12145,12154,12195,12199,12204,12228,12240,12244,12249,12253,12257,12262,12284,12294,12298,12302,12306,12310,12315,12352,12356,12361,12373,12390,12402,12412,12417,12421,12426,12448,12452,12457,12478,12486,12494,12502,12510,12514,12526,12530,12535,12562,12580,12585,12609,12613,12624,12628,12633,12637,12641,12662,12667,12678,12682,12692,12696,12700,12704,12708,12713,12721,12726,12731],{"__ignoreMap":227},[1397,12146,12147,12149,12152],{"class":1399,"line":1400},[1397,12148,8618],{"class":1407},[1397,12150,12151],{"class":1414}," TokenService",[1397,12153,2493],{"class":1403},[1397,12155,12156,12158,12161,12163,12166,12168,12170,12173,12176,12178,12180,12183,12185,12187,12189,12191,12193],{"class":1399,"line":228},[1397,12157,12039],{"class":1407},[1397,12159,12160],{"class":1407}," static",[1397,12162,10075],{"class":1407},[1397,12164,12165],{"class":1414}," getToken",[1397,12167,1435],{"class":1403},[1397,12169,3984],{"class":1414},[1397,12171,12172],{"class":1403},": Config, ",[1397,12174,12175],{"class":1414},"onComplete",[1397,12177,2513],{"class":1403},[1397,12179,11082],{"class":1407},[1397,12181,12182],{"class":1403}," (_ accessToken: ",[1397,12184,1520],{"class":1561},[1397,12186,10096],{"class":1407},[1397,12188,3991],{"class":1403},[1397,12190,10091],{"class":1407},[1397,12192,11090],{"class":1561},[1397,12194,1580],{"class":1403},[1397,12196,12197],{"class":1399,"line":1426},[1397,12198,1423],{"emptyLinePlaceholder":234},[1397,12200,12201],{"class":1399,"line":1451},[1397,12202,12203],{"class":3619},"        \u002F\u002F Get username param encoded\n",[1397,12205,12206,12209,12211,12214,12216,12219,12222,12224,12226],{"class":1399,"line":1470},[1397,12207,12208],{"class":1407},"        guard",[1397,12210,9153],{"class":1407},[1397,12212,12213],{"class":1403}," username ",[1397,12215,1408],{"class":1407},[1397,12217,12218],{"class":1403}," config.clientId.",[1397,12220,12221],{"class":1561},"encodeForAuth",[1397,12223,10433],{"class":1403},[1397,12225,10184],{"class":1407},[1397,12227,2493],{"class":1403},[1397,12229,12230,12233,12235,12238],{"class":1399,"line":1489},[1397,12231,12232],{"class":1561},"            onComplete",[1397,12234,1435],{"class":1403},[1397,12236,12237],{"class":1561},"nil",[1397,12239,2549],{"class":1403},[1397,12241,12242],{"class":1399,"line":1651},[1397,12243,1423],{"emptyLinePlaceholder":234},[1397,12245,12246],{"class":1399,"line":1675},[1397,12247,12248],{"class":1407},"            return\n",[1397,12250,12251],{"class":1399,"line":1687},[1397,12252,3427],{"class":1403},[1397,12254,12255],{"class":1399,"line":2275},[1397,12256,1423],{"emptyLinePlaceholder":234},[1397,12258,12259],{"class":1399,"line":2281},[1397,12260,12261],{"class":3619},"        \u002F\u002F Get password param encoded\n",[1397,12263,12264,12266,12268,12271,12273,12276,12278,12280,12282],{"class":1399,"line":2540},[1397,12265,12208],{"class":1407},[1397,12267,9153],{"class":1407},[1397,12269,12270],{"class":1403}," password ",[1397,12272,1408],{"class":1407},[1397,12274,12275],{"class":1403}," config.clientSecret.",[1397,12277,12221],{"class":1561},[1397,12279,10433],{"class":1403},[1397,12281,10184],{"class":1407},[1397,12283,2493],{"class":1403},[1397,12285,12286,12288,12290,12292],{"class":1399,"line":2552},[1397,12287,12232],{"class":1561},[1397,12289,1435],{"class":1403},[1397,12291,12237],{"class":1561},[1397,12293,2549],{"class":1403},[1397,12295,12296],{"class":1399,"line":2557},[1397,12297,1423],{"emptyLinePlaceholder":234},[1397,12299,12300],{"class":1399,"line":2575},[1397,12301,12248],{"class":1407},[1397,12303,12304],{"class":1399,"line":2580},[1397,12305,3427],{"class":1403},[1397,12307,12308],{"class":1399,"line":2594},[1397,12309,1423],{"emptyLinePlaceholder":234},[1397,12311,12312],{"class":1399,"line":2607},[1397,12313,12314],{"class":3619},"        \u002F\u002F Construct the basic auth string\n",[1397,12316,12317,12319,12322,12324,12327,12329,12331,12334,12336,12339,12341,12343,12345,12347,12350],{"class":1399,"line":2627},[1397,12318,12059],{"class":1407},[1397,12320,12321],{"class":1403}," basicAuth ",[1397,12323,1408],{"class":1407},[1397,12325,12326],{"class":1561}," Data",[1397,12328,1435],{"class":1403},[1397,12330,3420],{"class":1438},[1397,12332,12333],{"class":1438},"\\(username)",[1397,12335,1981],{"class":1438},[1397,12337,12338],{"class":1438},"\\(password)",[1397,12340,3420],{"class":1438},[1397,12342,1133],{"class":1403},[1397,12344,9175],{"class":1561},[1397,12346,1713],{"class":1403},[1397,12348,12349],{"class":1561},"base64EncodedString",[1397,12351,2572],{"class":1403},[1397,12353,12354],{"class":1399,"line":2644},[1397,12355,1423],{"emptyLinePlaceholder":234},[1397,12357,12358],{"class":1399,"line":2649},[1397,12359,12360],{"class":3619},"        \u002F\u002F Headers required\n",[1397,12362,12363,12365,12368,12370],{"class":1399,"line":2675},[1397,12364,12059],{"class":1407},[1397,12366,12367],{"class":1403}," headers: HTTPHeaders ",[1397,12369,1408],{"class":1407},[1397,12371,12372],{"class":1403}," [\n",[1397,12374,12375,12378,12380,12383,12386,12388],{"class":1399,"line":2680},[1397,12376,12377],{"class":1438},"            \"Authorization\"",[1397,12379,1981],{"class":1407},[1397,12381,12382],{"class":1438}," \"Basic ",[1397,12384,12385],{"class":1438},"\\(basicAuth)",[1397,12387,3420],{"class":1438},[1397,12389,2242],{"class":1403},[1397,12391,12392,12395,12397,12400],{"class":1399,"line":2693},[1397,12393,12394],{"class":1438},"            \"Accept\"",[1397,12396,1981],{"class":1407},[1397,12398,12399],{"class":1438}," \"application\u002Fjson\"",[1397,12401,2242],{"class":1403},[1397,12403,12404,12407,12409],{"class":1399,"line":2698},[1397,12405,12406],{"class":1438},"            \"Content-Type\"",[1397,12408,1981],{"class":1407},[1397,12410,12411],{"class":1438}," \"application\u002Fx-www-form-urlencoded\"\n",[1397,12413,12414],{"class":1399,"line":2719},[1397,12415,12416],{"class":1403},"        ]\n",[1397,12418,12419],{"class":1399,"line":2737},[1397,12420,1423],{"emptyLinePlaceholder":234},[1397,12422,12423],{"class":1399,"line":2753},[1397,12424,12425],{"class":3619},"        \u002F\u002F Body params\n",[1397,12427,12428,12430,12433,12435,12438,12441,12443,12446],{"class":1399,"line":2758},[1397,12429,12059],{"class":1407},[1397,12431,12432],{"class":1403}," parameters ",[1397,12434,1408],{"class":1407},[1397,12436,12437],{"class":1403}," [",[1397,12439,12440],{"class":1438},"\"grant_type\"",[1397,12442,1981],{"class":1407},[1397,12444,12445],{"class":1438}," \"client_credentials\"",[1397,12447,3929],{"class":1403},[1397,12449,12450],{"class":1399,"line":2778},[1397,12451,1423],{"emptyLinePlaceholder":234},[1397,12453,12454],{"class":1399,"line":2783},[1397,12455,12456],{"class":3619},"        \u002F\u002F Construct the request - setting the body to x-www-form-urlencoded\n",[1397,12458,12459,12461,12464,12466,12469,12471,12473,12476],{"class":1399,"line":2806},[1397,12460,12059],{"class":1407},[1397,12462,12463],{"class":1403}," request ",[1397,12465,1408],{"class":1407},[1397,12467,12468],{"class":1403}," AF.",[1397,12470,12132],{"class":1561},[1397,12472,1435],{"class":1403},[1397,12474,12475],{"class":1438},"\"https:\u002F\u002Fauth.sbanken.no\u002Fidentityserver\u002Fconnect\u002Ftoken\"",[1397,12477,2242],{"class":1403},[1397,12479,12480,12483],{"class":1399,"line":2811},[1397,12481,12482],{"class":1561},"                                 method",[1397,12484,12485],{"class":1403},": .post,\n",[1397,12487,12488,12491],{"class":1399,"line":2816},[1397,12489,12490],{"class":1561},"                                 parameters",[1397,12492,12493],{"class":1403},": parameters,\n",[1397,12495,12496,12499],{"class":1399,"line":2832},[1397,12497,12498],{"class":1561},"                                 encoding",[1397,12500,12501],{"class":1403},": URLEncoding.httpBody,\n",[1397,12503,12504,12507],{"class":1399,"line":2837},[1397,12505,12506],{"class":1561},"                                 headers",[1397,12508,12509],{"class":1403},": headers)\n",[1397,12511,12512],{"class":1399,"line":2847},[1397,12513,1423],{"emptyLinePlaceholder":234},[1397,12515,12516,12518,12520,12522,12524],{"class":1399,"line":2852},[1397,12517,12059],{"class":1407},[1397,12519,9184],{"class":1403},[1397,12521,1408],{"class":1407},[1397,12523,9189],{"class":1561},[1397,12525,2572],{"class":1403},[1397,12527,12528],{"class":1399,"line":2858},[1397,12529,1423],{"emptyLinePlaceholder":234},[1397,12531,12532],{"class":1399,"line":3435},[1397,12533,12534],{"class":3619},"        \u002F\u002F Call the API\n",[1397,12536,12537,12540,12543,12545,12548,12551,12553,12555,12557,12560],{"class":1399,"line":3446},[1397,12538,12539],{"class":1403},"        request.",[1397,12541,12542],{"class":1561},"responseDecodable",[1397,12544,1435],{"class":1403},[1397,12546,12547],{"class":1561},"of",[1397,12549,12550],{"class":1403},": Token.",[1397,12552,9222],{"class":1407},[1397,12554,1442],{"class":1403},[1397,12556,10049],{"class":1561},[1397,12558,12559],{"class":1403},": decoder) { (response) ",[1397,12561,9022],{"class":1407},[1397,12563,12564,12566,12568,12571,12573,12576,12578],{"class":1399,"line":3452},[1397,12565,10146],{"class":1407},[1397,12567,9153],{"class":1407},[1397,12569,12570],{"class":1403}," error ",[1397,12572,1408],{"class":1407},[1397,12574,12575],{"class":1403}," response.",[1397,12577,11136],{"class":1561},[1397,12579,2493],{"class":1403},[1397,12581,12582],{"class":1399,"line":3457},[1397,12583,12584],{"class":3619},"                \u002F\u002F It went wrong\n",[1397,12586,12587,12589,12591,12594,12597,12600,12603,12605,12607],{"class":1399,"line":3462},[1397,12588,10191],{"class":1561},[1397,12590,1435],{"class":1403},[1397,12592,12593],{"class":1438},"\"Unable to fetch token ",[1397,12595,12596],{"class":1438},"\\(response)",[1397,12598,12599],{"class":1438}," \\(error.",[1397,12601,12602],{"class":1403},"localizedDescription",[1397,12604,5321],{"class":1438},[1397,12606,3420],{"class":1438},[1397,12608,2549],{"class":1403},[1397,12610,12611],{"class":1399,"line":4534},[1397,12612,1423],{"emptyLinePlaceholder":234},[1397,12614,12615,12618,12620,12622],{"class":1399,"line":4539},[1397,12616,12617],{"class":1561},"                onComplete",[1397,12619,1435],{"class":1403},[1397,12621,12237],{"class":1561},[1397,12623,2549],{"class":1403},[1397,12625,12626],{"class":1399,"line":5019},[1397,12627,1423],{"emptyLinePlaceholder":234},[1397,12629,12630],{"class":1399,"line":5024},[1397,12631,12632],{"class":1407},"                return\n",[1397,12634,12635],{"class":1399,"line":5036},[1397,12636,10008],{"class":1403},[1397,12638,12639],{"class":1399,"line":5057},[1397,12640,1423],{"emptyLinePlaceholder":234},[1397,12642,12643,12646,12648,12651,12653,12655,12657,12660],{"class":1399,"line":5062},[1397,12644,12645],{"class":1407},"            guard",[1397,12647,9153],{"class":1407},[1397,12649,12650],{"class":1403}," token ",[1397,12652,1408],{"class":1407},[1397,12654,12575],{"class":1403},[1397,12656,2015],{"class":1561},[1397,12658,12659],{"class":1407}," else",[1397,12661,2493],{"class":1403},[1397,12663,12664],{"class":1399,"line":5074},[1397,12665,12666],{"class":3619},"                \u002F\u002F We got an answer but could not parse it\n",[1397,12668,12669,12671,12673,12676],{"class":1399,"line":5098},[1397,12670,10191],{"class":1561},[1397,12672,1435],{"class":1403},[1397,12674,12675],{"class":1438},"\"Unable to read token\"",[1397,12677,2549],{"class":1403},[1397,12679,12680],{"class":1399,"line":5104},[1397,12681,1423],{"emptyLinePlaceholder":234},[1397,12683,12684,12686,12688,12690],{"class":1399,"line":5109},[1397,12685,12617],{"class":1561},[1397,12687,1435],{"class":1403},[1397,12689,12237],{"class":1561},[1397,12691,2549],{"class":1403},[1397,12693,12694],{"class":1399,"line":5119},[1397,12695,1423],{"emptyLinePlaceholder":234},[1397,12697,12698],{"class":1399,"line":5125},[1397,12699,12632],{"class":1407},[1397,12701,12702],{"class":1399,"line":5130},[1397,12703,10008],{"class":1403},[1397,12705,12706],{"class":1399,"line":5135},[1397,12707,1423],{"emptyLinePlaceholder":234},[1397,12709,12710],{"class":1399,"line":5140},[1397,12711,12712],{"class":3619},"            \u002F\u002F Token received\n",[1397,12714,12716,12718],{"class":1399,"line":12715},61,[1397,12717,12232],{"class":1561},[1397,12719,12720],{"class":1403},"(token.accessToken)\n",[1397,12722,12724],{"class":1399,"line":12723},62,[1397,12725,3427],{"class":1403},[1397,12727,12729],{"class":1399,"line":12728},63,[1397,12730,2023],{"class":1403},[1397,12732,12734],{"class":1399,"line":12733},64,[1397,12735,1690],{"class":1403},[8206,12737],{},[1254,12739,12741],{"id":12740},"triggering-the-get-token-call","Triggering the get token call",[211,12743,12744],{},"For now - in the main view - in the onAppear - we load config and ensure that you are logged in.",[211,12746,12747],{},"Add the following function to ContentView - it will call the service if the config is available and then print to console what we got back",[1317,12749,12751],{"className":8610,"code":12750,"language":8332,"meta":227,"style":227},"    func getToken() {\n        if let config = self.config {\n            TokenService.getToken(config: config) { (accessToken) in\n                print(\"\\(accessToken ?? \"No token\")\")\n            }\n        }\n    }\n",[1296,12752,12753,12762,12777,12794,12817,12821,12825],{"__ignoreMap":227},[1397,12754,12755,12758,12760],{"class":1399,"line":1400},[1397,12756,12757],{"class":1407},"    func",[1397,12759,12165],{"class":1414},[1397,12761,10293],{"class":1403},[1397,12763,12764,12766,12768,12770,12772,12774],{"class":1399,"line":228},[1397,12765,10103],{"class":1407},[1397,12767,9153],{"class":1407},[1397,12769,9205],{"class":1403},[1397,12771,1408],{"class":1407},[1397,12773,11529],{"class":1561},[1397,12775,12776],{"class":1403},".config {\n",[1397,12778,12779,12782,12785,12787,12789,12792],{"class":1399,"line":1426},[1397,12780,12781],{"class":1403},"            TokenService.",[1397,12783,12784],{"class":1561},"getToken",[1397,12786,1435],{"class":1403},[1397,12788,3984],{"class":1561},[1397,12790,12791],{"class":1403},": config) { (accessToken) ",[1397,12793,9022],{"class":1407},[1397,12795,12796,12798,12800,12802,12805,12808,12811,12813,12815],{"class":1399,"line":1451},[1397,12797,10191],{"class":1561},[1397,12799,1435],{"class":1403},[1397,12801,3420],{"class":1438},[1397,12803,12804],{"class":1438},"\\(accessToken ",[1397,12806,12807],{"class":1407},"??",[1397,12809,12810],{"class":1438}," \"No token\"",[1397,12812,5321],{"class":1438},[1397,12814,3420],{"class":1438},[1397,12816,2549],{"class":1403},[1397,12818,12819],{"class":1399,"line":1470},[1397,12820,10008],{"class":1403},[1397,12822,12823],{"class":1399,"line":1489},[1397,12824,3427],{"class":1403},[1397,12826,12827],{"class":1399,"line":1651},[1397,12828,2023],{"class":1403},[211,12830,12831,12832,12835],{},"Finally - for testing - we can add ",[1296,12833,12834],{},"self.getToken()"," to the OK clause of askForAuth():",[1317,12837,12839],{"className":8610,"code":12838,"language":8332,"meta":227,"style":227},"            case .OK:\n                self.authenticated = true\n                self.getToken()\n\n",[1296,12840,12841,12850,12860],{"__ignoreMap":227},[1397,12842,12843,12846,12848],{"class":1399,"line":1400},[1397,12844,12845],{"class":1407},"            case",[1397,12847,11387],{"class":1403},[1397,12849,9052],{"class":1407},[1397,12851,12852,12854,12856,12858],{"class":1399,"line":228},[1397,12853,9997],{"class":1561},[1397,12855,11396],{"class":1403},[1397,12857,1408],{"class":1407},[1397,12859,8891],{"class":1561},[1397,12861,12862,12864,12866,12868],{"class":1399,"line":1426},[1397,12863,9997],{"class":1561},[1397,12865,1133],{"class":1403},[1397,12867,12784],{"class":1561},[1397,12869,2572],{"class":1403},[211,12871,12872],{},"This will have to be updated later - but for now it will allow us to trigger the call for testing.",[8206,12874],{},[1254,12876,2061],{"id":2060},[211,12878,12879],{},"So - we now have a token. The next step will be to add support for fetching account info and transaction info.",[8206,12881],{},[211,12883,12884],{},[1118,12885,8315],{"href":8313,"rel":12886,"target":1125},[1122,1123,1124],[2066,12888,12889],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":227,"searchDepth":228,"depth":228,"links":12891},[12892,12896,12897],{"id":11848,"depth":228,"text":11849,"children":12893},[12894,12895],{"id":12009,"depth":1426,"text":12010},{"id":12132,"depth":1426,"text":12133},{"id":12740,"depth":228,"text":12741},{"id":2060,"depth":228,"text":2061},"2020-03-18 09:21 +0100","The app so far has the ability to read its configuration and to check that you are who you say you are on devices that support touch or face id. The next step is to get an access token from the S'banken API.",{},"\u002F2020\u002F03\u002F18\u002Ffetching-the-token",{"title":11835,"description":11840},{"loc":12901},"2020\u002F03\u002F18\u002Ffetching-the-token",[8331,8332,8333,8334,8329],"uulCaTg84KoDbZSwF4dK-pdinVoo_Yhswl1KgBpAbEo",{"id":12908,"title":12909,"body":12910,"category":231,"date":15147,"description":12914,"embedImage":231,"extension":232,"image":14484,"intro":15148,"meta":15149,"navigation":234,"path":15150,"seo":15151,"series":8325,"sitemap":15152,"stem":15153,"tags":15154,"__hash__":15155},"content\u002F2020\u002F03\u002F30\u002Faccount-view.md","Account View",{"type":208,"value":12911,"toc":15135},[12912,12915,12918,12920,12924,12927,12930,12940,12951,12954,12992,12995,12998,13100,13102,13105,13108,13111,13353,13357,13360,13733,13735,13739,13742,13745,13854,13857,14024,14027,14029,14033,14036,14044,14048,14051,14474,14477,14480,14485,14488,14721,14724,14727,14856,14859,15115,15117,15119,15122,15125,15127,15132],[211,12913,12914],{},"The app so far has the ability to get an authentication token.",[211,12916,12917],{},"The next step is to actually use it.",[8206,12919],{},[1254,12921,12923],{"id":12922},"account-information","Account Information",[211,12925,12926],{},"We need to fetch the top level account overview.",[211,12928,12929],{},"This is a GET request with the following headers",[444,12931,12932,12934,12937],{},[447,12933,11860],{},[447,12935,12936],{},"Authorization: Bearer AUTH_TOKEN",[447,12938,12939],{},"customerId: USER_ID",[211,12941,12942,12943,12946,12947,12950],{},"where ",[1296,12944,12945],{},"AUTH_TOKEN"," is the token from the auth call and ",[1296,12948,12949],{},"USER_ID"," is the userId from the config object.",[211,12952,12953],{},"The format of the response is:",[1317,12955,12957],{"className":8542,"code":12956,"language":8544,"meta":227,"style":227},"{\n  \"availableItems\": Int,\n  \"items\": [Account]\n}\n",[1296,12958,12959,12963,12975,12988],{"__ignoreMap":227},[1397,12960,12961],{"class":1399,"line":1400},[1397,12962,8551],{"class":1403},[1397,12964,12965,12968,12970,12973],{"class":1399,"line":228},[1397,12966,12967],{"class":1561},"  \"availableItems\"",[1397,12969,2513],{"class":1403},[1397,12971,5353],{"class":12972},"s6RL2",[1397,12974,2242],{"class":1403},[1397,12976,12977,12980,12983,12986],{"class":1399,"line":1426},[1397,12978,12979],{"class":1561},"  \"items\"",[1397,12981,12982],{"class":1403},": [",[1397,12984,12985],{"class":12972},"Account",[1397,12987,3929],{"class":1403},[1397,12989,12990],{"class":1399,"line":1451},[1397,12991,1690],{"class":1403},[211,12993,12994],{},"There are some further error fields available. To be honest - for this small app - all we need is the items field.",[211,12996,12997],{},"An account looks like this:",[1317,12999,13001],{"className":8542,"code":13000,"language":8544,"meta":227,"style":227},"{\n  \"accountId\": \"String\",\n  \"accountNumber\": \"String\",\n  \"ownerCustomerId\": \"String\",\n  \"name\": \"String\",\n  \"accountType\": \"String\",\n  \"available\": Double,\n  \"balance\": Double,\n  \"creditLimit\": Double\n}\n",[1296,13002,13003,13007,13019,13030,13041,13052,13063,13075,13086,13096],{"__ignoreMap":227},[1397,13004,13005],{"class":1399,"line":1400},[1397,13006,8551],{"class":1403},[1397,13008,13009,13012,13014,13017],{"class":1399,"line":228},[1397,13010,13011],{"class":1561},"  \"accountId\"",[1397,13013,2513],{"class":1403},[1397,13015,13016],{"class":1438},"\"String\"",[1397,13018,2242],{"class":1403},[1397,13020,13021,13024,13026,13028],{"class":1399,"line":1426},[1397,13022,13023],{"class":1561},"  \"accountNumber\"",[1397,13025,2513],{"class":1403},[1397,13027,13016],{"class":1438},[1397,13029,2242],{"class":1403},[1397,13031,13032,13035,13037,13039],{"class":1399,"line":1451},[1397,13033,13034],{"class":1561},"  \"ownerCustomerId\"",[1397,13036,2513],{"class":1403},[1397,13038,13016],{"class":1438},[1397,13040,2242],{"class":1403},[1397,13042,13043,13046,13048,13050],{"class":1399,"line":1470},[1397,13044,13045],{"class":1561},"  \"name\"",[1397,13047,2513],{"class":1403},[1397,13049,13016],{"class":1438},[1397,13051,2242],{"class":1403},[1397,13053,13054,13057,13059,13061],{"class":1399,"line":1489},[1397,13055,13056],{"class":1561},"  \"accountType\"",[1397,13058,2513],{"class":1403},[1397,13060,13016],{"class":1438},[1397,13062,2242],{"class":1403},[1397,13064,13065,13068,13070,13073],{"class":1399,"line":1651},[1397,13066,13067],{"class":1561},"  \"available\"",[1397,13069,2513],{"class":1403},[1397,13071,13072],{"class":12972},"Double",[1397,13074,2242],{"class":1403},[1397,13076,13077,13080,13082,13084],{"class":1399,"line":1675},[1397,13078,13079],{"class":1561},"  \"balance\"",[1397,13081,2513],{"class":1403},[1397,13083,13072],{"class":12972},[1397,13085,2242],{"class":1403},[1397,13087,13088,13091,13093],{"class":1399,"line":1687},[1397,13089,13090],{"class":1561},"  \"creditLimit\"",[1397,13092,2513],{"class":1403},[1397,13094,13095],{"class":12972},"Double\n",[1397,13097,13098],{"class":1399,"line":2275},[1397,13099,1690],{"class":1403},[1698,13101,8533],{"id":8532},[211,13103,13104],{},"So - we'll start modelling this in a service file with model and method - similar to the TokenService. Create a new file called AccountService.swift.",[211,13106,13107],{},"At the top we're going to need to add an import for Alamofire.",[211,13109,13110],{},"Then we can model the structure we want. Here we will model the entire account object but just pull out the items list from the containing response:",[1317,13112,13114],{"className":8610,"code":13113,"language":8332,"meta":227,"style":227},"struct Account : Decodable {\n    let accountId: String\n    let accountNumber: String\n    let ownerCustomerId: String\n    let name: String\n    let accountType: String\n    let available: Double\n    let balance: Double\n    let creditLimit: Double\n\n    enum CodingKeys: String, CodingKey {\n        case accountId\n        case accountNumber\n        case ownerCustomerId\n        case name\n        case accountType\n        case available\n        case balance\n        case creditLimit\n    }\n\n}\n\nstruct Accounts : Decodable {\n    let accounts: [Account]\n\n    enum CodingKeys: String, CodingKey {\n        case accounts = \"items\"\n    }\n}\n",[1296,13115,13116,13129,13138,13147,13156,13165,13174,13183,13192,13201,13205,13221,13228,13235,13242,13249,13256,13263,13270,13277,13281,13285,13289,13293,13306,13313,13317,13333,13345,13349],{"__ignoreMap":227},[1397,13117,13118,13120,13123,13125,13127],{"class":1399,"line":1400},[1397,13119,9706],{"class":1407},[1397,13121,13122],{"class":1414}," Account",[1397,13124,8623],{"class":1403},[1397,13126,11908],{"class":1414},[1397,13128,8551],{"class":1403},[1397,13130,13131,13133,13136],{"class":1399,"line":228},[1397,13132,8633],{"class":1407},[1397,13134,13135],{"class":1403}," accountId: ",[1397,13137,3883],{"class":1561},[1397,13139,13140,13142,13145],{"class":1399,"line":1426},[1397,13141,8633],{"class":1407},[1397,13143,13144],{"class":1403}," accountNumber: ",[1397,13146,3883],{"class":1561},[1397,13148,13149,13151,13154],{"class":1399,"line":1451},[1397,13150,8633],{"class":1407},[1397,13152,13153],{"class":1403}," ownerCustomerId: ",[1397,13155,3883],{"class":1561},[1397,13157,13158,13160,13163],{"class":1399,"line":1470},[1397,13159,8633],{"class":1407},[1397,13161,13162],{"class":1403}," name: ",[1397,13164,3883],{"class":1561},[1397,13166,13167,13169,13172],{"class":1399,"line":1489},[1397,13168,8633],{"class":1407},[1397,13170,13171],{"class":1403}," accountType: ",[1397,13173,3883],{"class":1561},[1397,13175,13176,13178,13181],{"class":1399,"line":1651},[1397,13177,8633],{"class":1407},[1397,13179,13180],{"class":1403}," available: ",[1397,13182,13095],{"class":1561},[1397,13184,13185,13187,13190],{"class":1399,"line":1675},[1397,13186,8633],{"class":1407},[1397,13188,13189],{"class":1403}," balance: ",[1397,13191,13095],{"class":1561},[1397,13193,13194,13196,13199],{"class":1399,"line":1687},[1397,13195,8633],{"class":1407},[1397,13197,13198],{"class":1403}," creditLimit: ",[1397,13200,13095],{"class":1561},[1397,13202,13203],{"class":1399,"line":2275},[1397,13204,1423],{"emptyLinePlaceholder":234},[1397,13206,13207,13209,13211,13213,13215,13217,13219],{"class":1399,"line":2281},[1397,13208,11947],{"class":1407},[1397,13210,8685],{"class":1414},[1397,13212,2513],{"class":1403},[1397,13214,1520],{"class":1561},[1397,13216,1442],{"class":1403},[1397,13218,8694],{"class":1414},[1397,13220,8551],{"class":1403},[1397,13222,13223,13225],{"class":1399,"line":2540},[1397,13224,11384],{"class":1407},[1397,13226,13227],{"class":1561}," accountId\n",[1397,13229,13230,13232],{"class":1399,"line":2552},[1397,13231,11384],{"class":1407},[1397,13233,13234],{"class":1561}," accountNumber\n",[1397,13236,13237,13239],{"class":1399,"line":2557},[1397,13238,11384],{"class":1407},[1397,13240,13241],{"class":1561}," ownerCustomerId\n",[1397,13243,13244,13246],{"class":1399,"line":2575},[1397,13245,11384],{"class":1407},[1397,13247,13248],{"class":1561}," name\n",[1397,13250,13251,13253],{"class":1399,"line":2580},[1397,13252,11384],{"class":1407},[1397,13254,13255],{"class":1561}," accountType\n",[1397,13257,13258,13260],{"class":1399,"line":2594},[1397,13259,11384],{"class":1407},[1397,13261,13262],{"class":1561}," available\n",[1397,13264,13265,13267],{"class":1399,"line":2607},[1397,13266,11384],{"class":1407},[1397,13268,13269],{"class":1561}," balance\n",[1397,13271,13272,13274],{"class":1399,"line":2627},[1397,13273,11384],{"class":1407},[1397,13275,13276],{"class":1561}," creditLimit\n",[1397,13278,13279],{"class":1399,"line":2644},[1397,13280,2023],{"class":1403},[1397,13282,13283],{"class":1399,"line":2649},[1397,13284,1423],{"emptyLinePlaceholder":234},[1397,13286,13287],{"class":1399,"line":2675},[1397,13288,1690],{"class":1403},[1397,13290,13291],{"class":1399,"line":2680},[1397,13292,1423],{"emptyLinePlaceholder":234},[1397,13294,13295,13297,13300,13302,13304],{"class":1399,"line":2693},[1397,13296,9706],{"class":1407},[1397,13298,13299],{"class":1414}," Accounts",[1397,13301,8623],{"class":1403},[1397,13303,11908],{"class":1414},[1397,13305,8551],{"class":1403},[1397,13307,13308,13310],{"class":1399,"line":2698},[1397,13309,8633],{"class":1407},[1397,13311,13312],{"class":1403}," accounts: [Account]\n",[1397,13314,13315],{"class":1399,"line":2719},[1397,13316,1423],{"emptyLinePlaceholder":234},[1397,13318,13319,13321,13323,13325,13327,13329,13331],{"class":1399,"line":2737},[1397,13320,11947],{"class":1407},[1397,13322,8685],{"class":1414},[1397,13324,2513],{"class":1403},[1397,13326,1520],{"class":1561},[1397,13328,1442],{"class":1403},[1397,13330,8694],{"class":1414},[1397,13332,8551],{"class":1403},[1397,13334,13335,13337,13340,13342],{"class":1399,"line":2753},[1397,13336,11384],{"class":1407},[1397,13338,13339],{"class":1561}," accounts",[1397,13341,2215],{"class":1407},[1397,13343,13344],{"class":1438}," \"items\"\n",[1397,13346,13347],{"class":1399,"line":2758},[1397,13348,2023],{"class":1403},[1397,13350,13351],{"class":1399,"line":2778},[1397,13352,1690],{"class":1403},[1698,13354,13356],{"id":13355},"service-call","Service call",[211,13358,13359],{},"Let's add a method to do this. It will take a config object and the current token and return an optional Account (if we get a response and if we find one with a matching account number). It will also callback when complete:",[1317,13361,13363],{"className":8610,"code":13362,"language":8332,"meta":227,"style":227},"    public static func getAccountDetails(config: Config, token: String, onComplete: @escaping (_ account: Account?) -> Void) {\n        let decoder = JSONDecoder()\n\n        \u002F\u002F Set up the headers for token based bearer auth.\n        let headers: HTTPHeaders = [\n            \"Authorization\": \"Bearer \\(token)\",\n            \"Accept\": \"application\u002Fjson\",\n            \"customerId\": config.userId\n        ]\n\n        \u002F\u002F Call the service\n        let request = AF.request(\"https:\u002F\u002Fapi.sbanken.no\u002Fexec.bank\u002Fapi\u002Fv1\u002Faccounts\u002F\",\n                                 method: .get,\n                                 headers: headers)\n\n        \u002F\u002F Decode the response\n        request.responseDecodable(of: Accounts.self, decoder: decoder) { (response) in\n            if let error = response.error {\n                print(\"Unable to fetch accounts \\(response) \\(error.localizedDescription)\")\n\n                onComplete(nil)\n\n                return\n            }\n\n            guard let account = response.value?.accounts.filter({ (account) -> Bool in\n                account.accountNumber == config.accountNr\n            }).first else {\n                print(\"Unable to find account\")\n\n                onComplete(nil)\n\n                return\n            }\n\n            onComplete(account)\n        }\n    }\n",[1296,13364,13365,13410,13422,13426,13431,13441,13457,13467,13477,13481,13485,13490,13509,13516,13522,13526,13531,13554,13570,13591,13595,13605,13609,13613,13617,13621,13655,13665,13677,13688,13692,13702,13706,13710,13714,13718,13725,13729],{"__ignoreMap":227},[1397,13366,13367,13369,13371,13373,13376,13378,13380,13382,13385,13387,13389,13391,13393,13395,13397,13400,13402,13404,13406,13408],{"class":1399,"line":1400},[1397,13368,12039],{"class":1407},[1397,13370,12160],{"class":1407},[1397,13372,10075],{"class":1407},[1397,13374,13375],{"class":1414}," getAccountDetails",[1397,13377,1435],{"class":1403},[1397,13379,3984],{"class":1414},[1397,13381,12172],{"class":1403},[1397,13383,13384],{"class":1414},"token",[1397,13386,2513],{"class":1403},[1397,13388,1520],{"class":1561},[1397,13390,1442],{"class":1403},[1397,13392,12175],{"class":1414},[1397,13394,2513],{"class":1403},[1397,13396,11082],{"class":1407},[1397,13398,13399],{"class":1403}," (_ account: Account",[1397,13401,10096],{"class":1407},[1397,13403,3991],{"class":1403},[1397,13405,10091],{"class":1407},[1397,13407,11090],{"class":1561},[1397,13409,1580],{"class":1403},[1397,13411,13412,13414,13416,13418,13420],{"class":1399,"line":228},[1397,13413,12059],{"class":1407},[1397,13415,9184],{"class":1403},[1397,13417,1408],{"class":1407},[1397,13419,9189],{"class":1561},[1397,13421,2572],{"class":1403},[1397,13423,13424],{"class":1399,"line":1426},[1397,13425,1423],{"emptyLinePlaceholder":234},[1397,13427,13428],{"class":1399,"line":1451},[1397,13429,13430],{"class":3619},"        \u002F\u002F Set up the headers for token based bearer auth.\n",[1397,13432,13433,13435,13437,13439],{"class":1399,"line":1470},[1397,13434,12059],{"class":1407},[1397,13436,12367],{"class":1403},[1397,13438,1408],{"class":1407},[1397,13440,12372],{"class":1403},[1397,13442,13443,13445,13447,13450,13453,13455],{"class":1399,"line":1489},[1397,13444,12377],{"class":1438},[1397,13446,1981],{"class":1407},[1397,13448,13449],{"class":1438}," \"Bearer ",[1397,13451,13452],{"class":1438},"\\(token)",[1397,13454,3420],{"class":1438},[1397,13456,2242],{"class":1403},[1397,13458,13459,13461,13463,13465],{"class":1399,"line":1651},[1397,13460,12394],{"class":1438},[1397,13462,1981],{"class":1407},[1397,13464,12399],{"class":1438},[1397,13466,2242],{"class":1403},[1397,13468,13469,13472,13474],{"class":1399,"line":1675},[1397,13470,13471],{"class":1438},"            \"customerId\"",[1397,13473,1981],{"class":1407},[1397,13475,13476],{"class":1403}," config.userId\n",[1397,13478,13479],{"class":1399,"line":1687},[1397,13480,12416],{"class":1403},[1397,13482,13483],{"class":1399,"line":2275},[1397,13484,1423],{"emptyLinePlaceholder":234},[1397,13486,13487],{"class":1399,"line":2281},[1397,13488,13489],{"class":3619},"        \u002F\u002F Call the service\n",[1397,13491,13492,13494,13496,13498,13500,13502,13504,13507],{"class":1399,"line":2540},[1397,13493,12059],{"class":1407},[1397,13495,12463],{"class":1403},[1397,13497,1408],{"class":1407},[1397,13499,12468],{"class":1403},[1397,13501,12132],{"class":1561},[1397,13503,1435],{"class":1403},[1397,13505,13506],{"class":1438},"\"https:\u002F\u002Fapi.sbanken.no\u002Fexec.bank\u002Fapi\u002Fv1\u002Faccounts\u002F\"",[1397,13508,2242],{"class":1403},[1397,13510,13511,13513],{"class":1399,"line":2552},[1397,13512,12482],{"class":1561},[1397,13514,13515],{"class":1403},": .get,\n",[1397,13517,13518,13520],{"class":1399,"line":2557},[1397,13519,12506],{"class":1561},[1397,13521,12509],{"class":1403},[1397,13523,13524],{"class":1399,"line":2575},[1397,13525,1423],{"emptyLinePlaceholder":234},[1397,13527,13528],{"class":1399,"line":2580},[1397,13529,13530],{"class":3619},"        \u002F\u002F Decode the response\n",[1397,13532,13533,13535,13537,13539,13541,13544,13546,13548,13550,13552],{"class":1399,"line":2594},[1397,13534,12539],{"class":1403},[1397,13536,12542],{"class":1561},[1397,13538,1435],{"class":1403},[1397,13540,12547],{"class":1561},[1397,13542,13543],{"class":1403},": Accounts.",[1397,13545,9222],{"class":1407},[1397,13547,1442],{"class":1403},[1397,13549,10049],{"class":1561},[1397,13551,12559],{"class":1403},[1397,13553,9022],{"class":1407},[1397,13555,13556,13558,13560,13562,13564,13566,13568],{"class":1399,"line":2607},[1397,13557,10146],{"class":1407},[1397,13559,9153],{"class":1407},[1397,13561,12570],{"class":1403},[1397,13563,1408],{"class":1407},[1397,13565,12575],{"class":1403},[1397,13567,11136],{"class":1561},[1397,13569,2493],{"class":1403},[1397,13571,13572,13574,13576,13579,13581,13583,13585,13587,13589],{"class":1399,"line":2627},[1397,13573,10191],{"class":1561},[1397,13575,1435],{"class":1403},[1397,13577,13578],{"class":1438},"\"Unable to fetch accounts ",[1397,13580,12596],{"class":1438},[1397,13582,12599],{"class":1438},[1397,13584,12602],{"class":1403},[1397,13586,5321],{"class":1438},[1397,13588,3420],{"class":1438},[1397,13590,2549],{"class":1403},[1397,13592,13593],{"class":1399,"line":2644},[1397,13594,1423],{"emptyLinePlaceholder":234},[1397,13596,13597,13599,13601,13603],{"class":1399,"line":2649},[1397,13598,12617],{"class":1561},[1397,13600,1435],{"class":1403},[1397,13602,12237],{"class":1561},[1397,13604,2549],{"class":1403},[1397,13606,13607],{"class":1399,"line":2675},[1397,13608,1423],{"emptyLinePlaceholder":234},[1397,13610,13611],{"class":1399,"line":2680},[1397,13612,12632],{"class":1407},[1397,13614,13615],{"class":1399,"line":2693},[1397,13616,10008],{"class":1403},[1397,13618,13619],{"class":1399,"line":2698},[1397,13620,1423],{"emptyLinePlaceholder":234},[1397,13622,13623,13625,13627,13630,13632,13634,13636,13638,13641,13644,13647,13649,13652],{"class":1399,"line":2719},[1397,13624,12645],{"class":1407},[1397,13626,9153],{"class":1407},[1397,13628,13629],{"class":1403}," account ",[1397,13631,1408],{"class":1407},[1397,13633,12575],{"class":1403},[1397,13635,2015],{"class":1561},[1397,13637,10096],{"class":1407},[1397,13639,13640],{"class":1403},".accounts.",[1397,13642,13643],{"class":1561},"filter",[1397,13645,13646],{"class":1403},"({ (account) ",[1397,13648,10091],{"class":1407},[1397,13650,13651],{"class":1561}," Bool",[1397,13653,13654],{"class":1407}," in\n",[1397,13656,13657,13660,13662],{"class":1399,"line":2737},[1397,13658,13659],{"class":1403},"                account.accountNumber ",[1397,13661,11534],{"class":1407},[1397,13663,13664],{"class":1403}," config.accountNr\n",[1397,13666,13667,13670,13673,13675],{"class":1399,"line":2753},[1397,13668,13669],{"class":1403},"            }).",[1397,13671,13672],{"class":1561},"first",[1397,13674,12659],{"class":1407},[1397,13676,2493],{"class":1403},[1397,13678,13679,13681,13683,13686],{"class":1399,"line":2758},[1397,13680,10191],{"class":1561},[1397,13682,1435],{"class":1403},[1397,13684,13685],{"class":1438},"\"Unable to find account\"",[1397,13687,2549],{"class":1403},[1397,13689,13690],{"class":1399,"line":2778},[1397,13691,1423],{"emptyLinePlaceholder":234},[1397,13693,13694,13696,13698,13700],{"class":1399,"line":2783},[1397,13695,12617],{"class":1561},[1397,13697,1435],{"class":1403},[1397,13699,12237],{"class":1561},[1397,13701,2549],{"class":1403},[1397,13703,13704],{"class":1399,"line":2806},[1397,13705,1423],{"emptyLinePlaceholder":234},[1397,13707,13708],{"class":1399,"line":2811},[1397,13709,12632],{"class":1407},[1397,13711,13712],{"class":1399,"line":2816},[1397,13713,10008],{"class":1403},[1397,13715,13716],{"class":1399,"line":2832},[1397,13717,1423],{"emptyLinePlaceholder":234},[1397,13719,13720,13722],{"class":1399,"line":2837},[1397,13721,12232],{"class":1561},[1397,13723,13724],{"class":1403},"(account)\n",[1397,13726,13727],{"class":1399,"line":2847},[1397,13728,3427],{"class":1403},[1397,13730,13731],{"class":1399,"line":2852},[1397,13732,2023],{"class":1403},[8206,13734],{},[1254,13736,13738],{"id":13737},"triggering-the-call","Triggering the call",[211,13740,13741],{},"For now - we'll just trigger the call in the callback of the get token method. There are better ways to structure this - but for now this is simple to do and will cover the simple needs of the app.",[211,13743,13744],{},"Add a property on the view to hold the account. We cheat slightly here - using the dummy instance to set up \"default\" values for the view (currently just the name):",[1317,13746,13748],{"className":8610,"code":13747,"language":8332,"meta":227,"style":227},"@State private var account = Account(accountId: \"\",\n                                         accountNumber: \"\",\n                                         ownerCustomerId: \"\",\n                                         name: \"Lommepenger\",\n                                         accountType: \"\",\n                                         available: 0.0,\n                                         balance: 0.0,\n                                         creditLimit: 0.0)\n",[1296,13749,13750,13775,13786,13797,13809,13820,13832,13843],{"__ignoreMap":227},[1397,13751,13752,13754,13756,13758,13760,13762,13764,13766,13769,13771,13773],{"class":1399,"line":1400},[1397,13753,8836],{"class":1407},[1397,13755,8839],{"class":1407},[1397,13757,8842],{"class":1407},[1397,13759,13629],{"class":1403},[1397,13761,1408],{"class":1407},[1397,13763,13122],{"class":1561},[1397,13765,1435],{"class":1403},[1397,13767,13768],{"class":1561},"accountId",[1397,13770,2513],{"class":1403},[1397,13772,9631],{"class":1438},[1397,13774,2242],{"class":1403},[1397,13776,13777,13780,13782,13784],{"class":1399,"line":228},[1397,13778,13779],{"class":1561},"                                         accountNumber",[1397,13781,2513],{"class":1403},[1397,13783,9631],{"class":1438},[1397,13785,2242],{"class":1403},[1397,13787,13788,13791,13793,13795],{"class":1399,"line":1426},[1397,13789,13790],{"class":1561},"                                         ownerCustomerId",[1397,13792,2513],{"class":1403},[1397,13794,9631],{"class":1438},[1397,13796,2242],{"class":1403},[1397,13798,13799,13802,13804,13807],{"class":1399,"line":1451},[1397,13800,13801],{"class":1561},"                                         name",[1397,13803,2513],{"class":1403},[1397,13805,13806],{"class":1438},"\"Lommepenger\"",[1397,13808,2242],{"class":1403},[1397,13810,13811,13814,13816,13818],{"class":1399,"line":1470},[1397,13812,13813],{"class":1561},"                                         accountType",[1397,13815,2513],{"class":1403},[1397,13817,9631],{"class":1438},[1397,13819,2242],{"class":1403},[1397,13821,13822,13825,13827,13830],{"class":1399,"line":1489},[1397,13823,13824],{"class":1561},"                                         available",[1397,13826,2513],{"class":1403},[1397,13828,13829],{"class":1561},"0.0",[1397,13831,2242],{"class":1403},[1397,13833,13834,13837,13839,13841],{"class":1399,"line":1651},[1397,13835,13836],{"class":1561},"                                         balance",[1397,13838,2513],{"class":1403},[1397,13840,13829],{"class":1561},[1397,13842,2242],{"class":1403},[1397,13844,13845,13848,13850,13852],{"class":1399,"line":1675},[1397,13846,13847],{"class":1561},"                                         creditLimit",[1397,13849,2513],{"class":1403},[1397,13851,13829],{"class":1561},[1397,13853,2549],{"class":1403},[211,13855,13856],{},"Update getToken in ContentView:",[1317,13858,13860],{"className":8610,"code":13859,"language":8332,"meta":227,"style":227},"func getToken() {\n    if let config = self.config {\n        TokenService.getToken(config: config) { (accessToken) in\n            if let token = accessToken {\n                AccountService.getAccountDetails(config: config, token: token) { (account) in\n                    if let account = account {\n                        self.account = account\n                    } else {\n                        print(\"No account\")\n                    }\n                }\n            } else {\n                print(\"No token\")\n            }\n        }\n    }\n}\n",[1296,13861,13862,13870,13884,13899,13912,13934,13948,13960,13969,13981,13985,13989,13997,14008,14012,14016,14020],{"__ignoreMap":227},[1397,13863,13864,13866,13868],{"class":1399,"line":1400},[1397,13865,9667],{"class":1407},[1397,13867,12165],{"class":1414},[1397,13869,10293],{"class":1403},[1397,13871,13872,13874,13876,13878,13880,13882],{"class":1399,"line":228},[1397,13873,9200],{"class":1407},[1397,13875,9153],{"class":1407},[1397,13877,9205],{"class":1403},[1397,13879,1408],{"class":1407},[1397,13881,11529],{"class":1561},[1397,13883,12776],{"class":1403},[1397,13885,13886,13889,13891,13893,13895,13897],{"class":1399,"line":1426},[1397,13887,13888],{"class":1403},"        TokenService.",[1397,13890,12784],{"class":1561},[1397,13892,1435],{"class":1403},[1397,13894,3984],{"class":1561},[1397,13896,12791],{"class":1403},[1397,13898,9022],{"class":1407},[1397,13900,13901,13903,13905,13907,13909],{"class":1399,"line":1451},[1397,13902,10146],{"class":1407},[1397,13904,9153],{"class":1407},[1397,13906,12650],{"class":1403},[1397,13908,1408],{"class":1407},[1397,13910,13911],{"class":1403}," accessToken {\n",[1397,13913,13914,13917,13920,13922,13924,13927,13929,13932],{"class":1399,"line":1470},[1397,13915,13916],{"class":1403},"                AccountService.",[1397,13918,13919],{"class":1561},"getAccountDetails",[1397,13921,1435],{"class":1403},[1397,13923,3984],{"class":1561},[1397,13925,13926],{"class":1403},": config, ",[1397,13928,13384],{"class":1561},[1397,13930,13931],{"class":1403},": token) { (account) ",[1397,13933,9022],{"class":1407},[1397,13935,13936,13939,13941,13943,13945],{"class":1399,"line":1489},[1397,13937,13938],{"class":1407},"                    if",[1397,13940,9153],{"class":1407},[1397,13942,13629],{"class":1403},[1397,13944,1408],{"class":1407},[1397,13946,13947],{"class":1403}," account {\n",[1397,13949,13950,13952,13955,13957],{"class":1399,"line":1651},[1397,13951,9880],{"class":1561},[1397,13953,13954],{"class":1403},".account ",[1397,13956,1408],{"class":1407},[1397,13958,13959],{"class":1403}," account\n",[1397,13961,13962,13965,13967],{"class":1399,"line":1675},[1397,13963,13964],{"class":1403},"                    } ",[1397,13966,10184],{"class":1407},[1397,13968,2493],{"class":1403},[1397,13970,13971,13974,13976,13979],{"class":1399,"line":1687},[1397,13972,13973],{"class":1561},"                        print",[1397,13975,1435],{"class":1403},[1397,13977,13978],{"class":1438},"\"No account\"",[1397,13980,2549],{"class":1403},[1397,13982,13983],{"class":1399,"line":2275},[1397,13984,9893],{"class":1403},[1397,13986,13987],{"class":1399,"line":2281},[1397,13988,9992],{"class":1403},[1397,13990,13991,13993,13995],{"class":1399,"line":2540},[1397,13992,10181],{"class":1403},[1397,13994,10184],{"class":1407},[1397,13996,2493],{"class":1403},[1397,13998,13999,14001,14003,14006],{"class":1399,"line":2552},[1397,14000,10191],{"class":1561},[1397,14002,1435],{"class":1403},[1397,14004,14005],{"class":1438},"\"No token\"",[1397,14007,2549],{"class":1403},[1397,14009,14010],{"class":1399,"line":2557},[1397,14011,10008],{"class":1403},[1397,14013,14014],{"class":1399,"line":2575},[1397,14015,3427],{"class":1403},[1397,14017,14018],{"class":1399,"line":2580},[1397,14019,2023],{"class":1403},[1397,14021,14022],{"class":1399,"line":2594},[1397,14023,1690],{"class":1403},[211,14025,14026],{},"So - when this is run - the view gets an account object.",[8206,14028],{},[1254,14030,14032],{"id":14031},"updating-the-view","Updating the view",[211,14034,14035],{},"We want to do two things:",[444,14037,14038,14041],{},[447,14039,14040],{},"Update ContentView to have a more suitable structure",[447,14042,14043],{},"Create an AccountView to hold all the account display info",[1698,14045,14047],{"id":14046},"content-view","Content View",[211,14049,14050],{},"We want to have the outermost part to be a NavigationView with the scan button top right. Restructure as follows:",[1317,14052,14054],{"className":8610,"code":14053,"language":8332,"meta":227,"style":227},"    var body: some View {\n        NavigationView {\n            VStack {\n                if (self.config != nil) {\n                    \u002F\u002F Do we have a config available?\n\n                    if (self.authenticated == false) {\n                        \u002F\u002F Waiting for Face\u002FTouch ID\n\n                        Text(\"Please authenticate\")\n                    } else {\n                        \u002F\u002F All good - show the account\n                        AccountView(account: account)\n                            \u002F\u002F and set the account name as the main page title\n                            .navigationBarTitle(Text(account.name), displayMode: .inline)\n                    }\n                } else {\n                    \u002F\u002F No config yet\n                    Text(\"You need to scan in a configuation\")\n                }\n            }\n            \u002F\u002F Scan button on nav bar top right\n            .navigationBarItems(trailing: Button(action: {\n                self.showingScanner = true\n            }) {\n                Image(systemName: \"qrcode\")\n                    .resizable()\n                    .frame(width: 32, height: 32)\n                }\n            )\n            \u002F\u002F Scan view sheet\n            .sheet(isPresented: $showingScanner) {\n                ScannerView(scannedData: Binding(\n                    get: { \"\" },\n                    set: self.newScanData\n                ))\n            }\n            \u002F\u002F Load config and auth on startup\n            .onAppear {\n                self.config = Config.loadConfig()\n\n                if (self.config != nil && self.authenticated == false) {\n                    self.askForAuth()\n                }\n            }\n        }\n    }\n",[1296,14055,14056,14066,14073,14080,14096,14101,14105,14121,14126,14130,14141,14149,14154,14167,14172,14194,14198,14206,14211,14222,14226,14230,14235,14258,14268,14273,14287,14295,14319,14323,14328,14333,14345,14360,14371,14382,14387,14391,14396,14404,14418,14422,14448,14458,14462,14466,14470],{"__ignoreMap":227},[1397,14057,14058,14060,14062,14064],{"class":1399,"line":1400},[1397,14059,9752],{"class":1407},[1397,14061,9755],{"class":1403},[1397,14063,9758],{"class":1407},[1397,14065,9761],{"class":1403},[1397,14067,14068,14071],{"class":1399,"line":228},[1397,14069,14070],{"class":1561},"        NavigationView",[1397,14072,2493],{"class":1403},[1397,14074,14075,14078],{"class":1399,"line":1426},[1397,14076,14077],{"class":1561},"            VStack",[1397,14079,2493],{"class":1403},[1397,14081,14082,14084,14086,14088,14090,14092,14094],{"class":1399,"line":1451},[1397,14083,11182],{"class":1407},[1397,14085,1550],{"class":1403},[1397,14087,9222],{"class":1561},[1397,14089,10605],{"class":1403},[1397,14091,10803],{"class":1407},[1397,14093,10806],{"class":1561},[1397,14095,1580],{"class":1403},[1397,14097,14098],{"class":1399,"line":1470},[1397,14099,14100],{"class":3619},"                    \u002F\u002F Do we have a config available?\n",[1397,14102,14103],{"class":1399,"line":1489},[1397,14104,1423],{"emptyLinePlaceholder":234},[1397,14106,14107,14109,14111,14113,14115,14117,14119],{"class":1399,"line":1651},[1397,14108,13938],{"class":1407},[1397,14110,1550],{"class":1403},[1397,14112,9222],{"class":1561},[1397,14114,11396],{"class":1403},[1397,14116,11534],{"class":1407},[1397,14118,11537],{"class":1561},[1397,14120,1580],{"class":1403},[1397,14122,14123],{"class":1399,"line":1675},[1397,14124,14125],{"class":3619},"                        \u002F\u002F Waiting for Face\u002FTouch ID\n",[1397,14127,14128],{"class":1399,"line":1687},[1397,14129,1423],{"emptyLinePlaceholder":234},[1397,14131,14132,14135,14137,14139],{"class":1399,"line":2275},[1397,14133,14134],{"class":1561},"                        Text",[1397,14136,1435],{"class":1403},[1397,14138,11731],{"class":1438},[1397,14140,2549],{"class":1403},[1397,14142,14143,14145,14147],{"class":1399,"line":2281},[1397,14144,13964],{"class":1403},[1397,14146,10184],{"class":1407},[1397,14148,2493],{"class":1403},[1397,14150,14151],{"class":1399,"line":2540},[1397,14152,14153],{"class":3619},"                        \u002F\u002F All good - show the account\n",[1397,14155,14156,14159,14161,14164],{"class":1399,"line":2552},[1397,14157,14158],{"class":1561},"                        AccountView",[1397,14160,1435],{"class":1403},[1397,14162,14163],{"class":1561},"account",[1397,14165,14166],{"class":1403},": account)\n",[1397,14168,14169],{"class":1399,"line":2557},[1397,14170,14171],{"class":3619},"                            \u002F\u002F and set the account name as the main page title\n",[1397,14173,14174,14177,14180,14182,14185,14188,14191],{"class":1399,"line":2575},[1397,14175,14176],{"class":1403},"                            .",[1397,14178,14179],{"class":1561},"navigationBarTitle",[1397,14181,1435],{"class":1403},[1397,14183,14184],{"class":1561},"Text",[1397,14186,14187],{"class":1403},"(account.name), ",[1397,14189,14190],{"class":1561},"displayMode",[1397,14192,14193],{"class":1403},": .inline)\n",[1397,14195,14196],{"class":1399,"line":2580},[1397,14197,9893],{"class":1403},[1397,14199,14200,14202,14204],{"class":1399,"line":2594},[1397,14201,11209],{"class":1403},[1397,14203,10184],{"class":1407},[1397,14205,2493],{"class":1403},[1397,14207,14208],{"class":1399,"line":2607},[1397,14209,14210],{"class":3619},"                    \u002F\u002F No config yet\n",[1397,14212,14213,14216,14218,14220],{"class":1399,"line":2627},[1397,14214,14215],{"class":1561},"                    Text",[1397,14217,1435],{"class":1403},[1397,14219,10839],{"class":1438},[1397,14221,2549],{"class":1403},[1397,14223,14224],{"class":1399,"line":2644},[1397,14225,9992],{"class":1403},[1397,14227,14228],{"class":1399,"line":2649},[1397,14229,10008],{"class":1403},[1397,14231,14232],{"class":1399,"line":2675},[1397,14233,14234],{"class":3619},"            \u002F\u002F Scan button on nav bar top right\n",[1397,14236,14237,14240,14243,14245,14248,14250,14252,14254,14256],{"class":1399,"line":2680},[1397,14238,14239],{"class":1403},"            .",[1397,14241,14242],{"class":1561},"navigationBarItems",[1397,14244,1435],{"class":1403},[1397,14246,14247],{"class":1561},"trailing",[1397,14249,2513],{"class":1403},[1397,14251,8870],{"class":1561},[1397,14253,1435],{"class":1403},[1397,14255,8875],{"class":1561},[1397,14257,8878],{"class":1403},[1397,14259,14260,14262,14264,14266],{"class":1399,"line":2693},[1397,14261,9997],{"class":1561},[1397,14263,8886],{"class":1403},[1397,14265,1408],{"class":1407},[1397,14267,8891],{"class":1561},[1397,14269,14270],{"class":1399,"line":2698},[1397,14271,14272],{"class":1403},"            }) {\n",[1397,14274,14275,14277,14279,14281,14283,14285],{"class":1399,"line":2719},[1397,14276,9822],{"class":1561},[1397,14278,1435],{"class":1403},[1397,14280,8906],{"class":1561},[1397,14282,2513],{"class":1403},[1397,14284,8911],{"class":1438},[1397,14286,2549],{"class":1403},[1397,14288,14289,14291,14293],{"class":1399,"line":2737},[1397,14290,9838],{"class":1403},[1397,14292,8921],{"class":1561},[1397,14294,2572],{"class":1403},[1397,14296,14297,14299,14301,14303,14305,14307,14309,14311,14313,14315,14317],{"class":1399,"line":2753},[1397,14298,9838],{"class":1403},[1397,14300,8930],{"class":1561},[1397,14302,1435],{"class":1403},[1397,14304,8935],{"class":1561},[1397,14306,2513],{"class":1403},[1397,14308,8940],{"class":1561},[1397,14310,1442],{"class":1403},[1397,14312,8945],{"class":1561},[1397,14314,2513],{"class":1403},[1397,14316,8940],{"class":1561},[1397,14318,2549],{"class":1403},[1397,14320,14321],{"class":1399,"line":2758},[1397,14322,9992],{"class":1403},[1397,14324,14325],{"class":1399,"line":2778},[1397,14326,14327],{"class":1403},"            )\n",[1397,14329,14330],{"class":1399,"line":2783},[1397,14331,14332],{"class":3619},"            \u002F\u002F Scan view sheet\n",[1397,14334,14335,14337,14339,14341,14343],{"class":1399,"line":2806},[1397,14336,14239],{"class":1403},[1397,14338,8970],{"class":1561},[1397,14340,1435],{"class":1403},[1397,14342,8975],{"class":1561},[1397,14344,8978],{"class":1403},[1397,14346,14347,14350,14352,14354,14356,14358],{"class":1399,"line":2811},[1397,14348,14349],{"class":1561},"                ScannerView",[1397,14351,1435],{"class":1403},[1397,14353,9613],{"class":1561},[1397,14355,2513],{"class":1403},[1397,14357,9618],{"class":1561},[1397,14359,2256],{"class":1403},[1397,14361,14362,14365,14367,14369],{"class":1399,"line":2816},[1397,14363,14364],{"class":1561},"                    get",[1397,14366,9628],{"class":1403},[1397,14368,9631],{"class":1438},[1397,14370,9634],{"class":1403},[1397,14372,14373,14376,14378,14380],{"class":1399,"line":2832},[1397,14374,14375],{"class":1561},"                    set",[1397,14377,2513],{"class":1403},[1397,14379,9222],{"class":1561},[1397,14381,9646],{"class":1403},[1397,14383,14384],{"class":1399,"line":2837},[1397,14385,14386],{"class":1403},"                ))\n",[1397,14388,14389],{"class":1399,"line":2847},[1397,14390,10008],{"class":1403},[1397,14392,14393],{"class":1399,"line":2852},[1397,14394,14395],{"class":3619},"            \u002F\u002F Load config and auth on startup\n",[1397,14397,14398,14400,14402],{"class":1399,"line":2858},[1397,14399,14239],{"class":1403},[1397,14401,10857],{"class":1561},[1397,14403,2493],{"class":1403},[1397,14405,14406,14408,14410,14412,14414,14416],{"class":1399,"line":3435},[1397,14407,9997],{"class":1561},[1397,14409,10605],{"class":1403},[1397,14411,1408],{"class":1407},[1397,14413,10577],{"class":1403},[1397,14415,10872],{"class":1561},[1397,14417,2572],{"class":1403},[1397,14419,14420],{"class":1399,"line":3446},[1397,14421,1423],{"emptyLinePlaceholder":234},[1397,14423,14424,14426,14428,14430,14432,14434,14436,14438,14440,14442,14444,14446],{"class":1399,"line":3452},[1397,14425,11182],{"class":1407},[1397,14427,1550],{"class":1403},[1397,14429,9222],{"class":1561},[1397,14431,10605],{"class":1403},[1397,14433,10803],{"class":1407},[1397,14435,10806],{"class":1561},[1397,14437,11526],{"class":1407},[1397,14439,11529],{"class":1561},[1397,14441,11396],{"class":1403},[1397,14443,11534],{"class":1407},[1397,14445,11537],{"class":1561},[1397,14447,1580],{"class":1403},[1397,14449,14450,14452,14454,14456],{"class":1399,"line":3457},[1397,14451,9956],{"class":1561},[1397,14453,1133],{"class":1403},[1397,14455,11548],{"class":1561},[1397,14457,2572],{"class":1403},[1397,14459,14460],{"class":1399,"line":3462},[1397,14461,9992],{"class":1403},[1397,14463,14464],{"class":1399,"line":4534},[1397,14465,10008],{"class":1403},[1397,14467,14468],{"class":1399,"line":4539},[1397,14469,3427],{"class":1403},[1397,14471,14472],{"class":1399,"line":5019},[1397,14473,2023],{"class":1403},[1698,14475,12909],{"id":14476},"account-view",[211,14478,14479],{},"To start with - we will show the account number, and the balance\u002Favailable figures. Something like this:",[211,14481,14482],{},[217,14483],{"alt":12909,"src":14484},"\u002Fimages\u002Fposts\u002F2020\u002F03\u002Faccount.png",[211,14486,14487],{},"Disponibelt - available. Saldo - balance.",[1317,14489,14491],{"className":8610,"code":14490,"language":8332,"meta":227,"style":227},"struct AccountView: View {\n    public var account : Account\n\n    var body: some View {\n        VStack {\n            Text(account.accountNumber.accountFormat())\n                .padding(.bottom)\n                .padding(.top)\n                .font(.caption)\n            HStack {\n                VStack {\n                    Text(\"Disponibelt\")\n                        .font(.caption)\n                    Text(account.available.currency())\n                }\n                .frame(maxWidth: .infinity)\n                VStack {\n                    Text(\"Saldo\")\n                        .font(.caption)\n                    Text(account.balance.currency())\n                }\n                .frame(maxWidth: .infinity)\n            }\n            Spacer()\n        }\n    }\n}\n",[1296,14492,14493,14506,14515,14519,14529,14535,14548,14557,14566,14575,14581,14588,14599,14608,14620,14624,14642,14648,14659,14667,14678,14682,14698,14702,14709,14713,14717],{"__ignoreMap":227},[1397,14494,14495,14497,14500,14502,14504],{"class":1399,"line":1400},[1397,14496,9706],{"class":1407},[1397,14498,14499],{"class":1414}," AccountView",[1397,14501,2513],{"class":1403},[1397,14503,9714],{"class":1414},[1397,14505,8551],{"class":1403},[1397,14507,14508,14510,14512],{"class":1399,"line":228},[1397,14509,12039],{"class":1407},[1397,14511,8842],{"class":1407},[1397,14513,14514],{"class":1403}," account : Account\n",[1397,14516,14517],{"class":1399,"line":1426},[1397,14518,1423],{"emptyLinePlaceholder":234},[1397,14520,14521,14523,14525,14527],{"class":1399,"line":1451},[1397,14522,9752],{"class":1407},[1397,14524,9755],{"class":1403},[1397,14526,9758],{"class":1407},[1397,14528,9761],{"class":1403},[1397,14530,14531,14533],{"class":1399,"line":1470},[1397,14532,9766],{"class":1561},[1397,14534,2493],{"class":1403},[1397,14536,14537,14539,14542,14545],{"class":1399,"line":1489},[1397,14538,10813],{"class":1561},[1397,14540,14541],{"class":1403},"(account.accountNumber.",[1397,14543,14544],{"class":1561},"accountFormat",[1397,14546,14547],{"class":1403},"())\n",[1397,14549,14550,14552,14554],{"class":1399,"line":1651},[1397,14551,10696],{"class":1403},[1397,14553,9803],{"class":1561},[1397,14555,14556],{"class":1403},"(.bottom)\n",[1397,14558,14559,14561,14563],{"class":1399,"line":1675},[1397,14560,10696],{"class":1403},[1397,14562,9803],{"class":1561},[1397,14564,14565],{"class":1403},"(.top)\n",[1397,14567,14568,14570,14572],{"class":1399,"line":1687},[1397,14569,10696],{"class":1403},[1397,14571,9797],{"class":1561},[1397,14573,14574],{"class":1403},"(.caption)\n",[1397,14576,14577,14579],{"class":1399,"line":2275},[1397,14578,9773],{"class":1561},[1397,14580,2493],{"class":1403},[1397,14582,14583,14586],{"class":1399,"line":2281},[1397,14584,14585],{"class":1561},"                VStack",[1397,14587,2493],{"class":1403},[1397,14589,14590,14592,14594,14597],{"class":1399,"line":2540},[1397,14591,14215],{"class":1561},[1397,14593,1435],{"class":1403},[1397,14595,14596],{"class":1438},"\"Disponibelt\"",[1397,14598,2549],{"class":1403},[1397,14600,14601,14604,14606],{"class":1399,"line":2552},[1397,14602,14603],{"class":1403},"                        .",[1397,14605,9797],{"class":1561},[1397,14607,14574],{"class":1403},[1397,14609,14610,14612,14615,14618],{"class":1399,"line":2557},[1397,14611,14215],{"class":1561},[1397,14613,14614],{"class":1403},"(account.available.",[1397,14616,14617],{"class":1561},"currency",[1397,14619,14547],{"class":1403},[1397,14621,14622],{"class":1399,"line":2575},[1397,14623,9992],{"class":1403},[1397,14625,14626,14628,14630,14632,14635,14637,14640],{"class":1399,"line":2580},[1397,14627,10696],{"class":1403},[1397,14629,8930],{"class":1561},[1397,14631,1435],{"class":1403},[1397,14633,14634],{"class":1561},"maxWidth",[1397,14636,9172],{"class":1403},[1397,14638,14639],{"class":1561},"infinity",[1397,14641,2549],{"class":1403},[1397,14643,14644,14646],{"class":1399,"line":2594},[1397,14645,14585],{"class":1561},[1397,14647,2493],{"class":1403},[1397,14649,14650,14652,14654,14657],{"class":1399,"line":2607},[1397,14651,14215],{"class":1561},[1397,14653,1435],{"class":1403},[1397,14655,14656],{"class":1438},"\"Saldo\"",[1397,14658,2549],{"class":1403},[1397,14660,14661,14663,14665],{"class":1399,"line":2627},[1397,14662,14603],{"class":1403},[1397,14664,9797],{"class":1561},[1397,14666,14574],{"class":1403},[1397,14668,14669,14671,14674,14676],{"class":1399,"line":2644},[1397,14670,14215],{"class":1561},[1397,14672,14673],{"class":1403},"(account.balance.",[1397,14675,14617],{"class":1561},[1397,14677,14547],{"class":1403},[1397,14679,14680],{"class":1399,"line":2649},[1397,14681,9992],{"class":1403},[1397,14683,14684,14686,14688,14690,14692,14694,14696],{"class":1399,"line":2675},[1397,14685,10696],{"class":1403},[1397,14687,8930],{"class":1561},[1397,14689,1435],{"class":1403},[1397,14691,14634],{"class":1561},[1397,14693,9172],{"class":1403},[1397,14695,14639],{"class":1561},[1397,14697,2549],{"class":1403},[1397,14699,14700],{"class":1399,"line":2680},[1397,14701,10008],{"class":1403},[1397,14703,14704,14707],{"class":1399,"line":2693},[1397,14705,14706],{"class":1561},"            Spacer",[1397,14708,2572],{"class":1403},[1397,14710,14711],{"class":1399,"line":2698},[1397,14712,3427],{"class":1403},[1397,14714,14715],{"class":1399,"line":2719},[1397,14716,2023],{"class":1403},[1397,14718,14719],{"class":1399,"line":2737},[1397,14720,1690],{"class":1403},[211,14722,14723],{},"Finally - we're using two extensions here.",[211,14725,14726],{},"Double currency() will return the value formatted for the current locale currency setting (in the simulator above - USD - on my son's iPhone it will be in NOK):",[1317,14728,14730],{"className":8610,"code":14729,"language":8332,"meta":227,"style":227},"extension Double {\n    func currency() -> String {\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .currency\n        guard let formatted = formatter.string(from: self as NSNumber) else {\n            return \"\\(self)\"\n        }\n\n        return formatted\n    }\n}\n",[1296,14731,14732,14741,14756,14770,14780,14815,14833,14837,14841,14848,14852],{"__ignoreMap":227},[1397,14733,14734,14736,14739],{"class":1399,"line":1400},[1397,14735,10063],{"class":1407},[1397,14737,14738],{"class":1561}," Double",[1397,14740,2493],{"class":1403},[1397,14742,14743,14745,14748,14750,14752,14754],{"class":1399,"line":228},[1397,14744,12757],{"class":1407},[1397,14746,14747],{"class":1414}," currency",[1397,14749,10433],{"class":1403},[1397,14751,10091],{"class":1407},[1397,14753,10367],{"class":1561},[1397,14755,2493],{"class":1403},[1397,14757,14758,14760,14763,14765,14768],{"class":1399,"line":1426},[1397,14759,12059],{"class":1407},[1397,14761,14762],{"class":1403}," formatter ",[1397,14764,1408],{"class":1407},[1397,14766,14767],{"class":1561}," NumberFormatter",[1397,14769,2572],{"class":1403},[1397,14771,14772,14775,14777],{"class":1399,"line":1451},[1397,14773,14774],{"class":1403},"        formatter.numberStyle ",[1397,14776,1408],{"class":1407},[1397,14778,14779],{"class":1403}," .currency\n",[1397,14781,14782,14784,14786,14789,14791,14794,14797,14799,14801,14803,14805,14808,14811,14813],{"class":1399,"line":1470},[1397,14783,12208],{"class":1407},[1397,14785,9153],{"class":1407},[1397,14787,14788],{"class":1403}," formatted ",[1397,14790,1408],{"class":1407},[1397,14792,14793],{"class":1403}," formatter.",[1397,14795,14796],{"class":1561},"string",[1397,14798,1435],{"class":1403},[1397,14800,9227],{"class":1561},[1397,14802,2513],{"class":1403},[1397,14804,9222],{"class":1561},[1397,14806,14807],{"class":1407}," as",[1397,14809,14810],{"class":1403}," NSNumber) ",[1397,14812,10184],{"class":1407},[1397,14814,2493],{"class":1403},[1397,14816,14817,14820,14823,14826,14828,14830],{"class":1399,"line":1489},[1397,14818,14819],{"class":1407},"            return",[1397,14821,14822],{"class":1438}," \"",[1397,14824,14825],{"class":1438},"\\(",[1397,14827,9222],{"class":1561},[1397,14829,5321],{"class":1438},[1397,14831,14832],{"class":1438},"\"\n",[1397,14834,14835],{"class":1399,"line":1651},[1397,14836,3427],{"class":1403},[1397,14838,14839],{"class":1399,"line":1675},[1397,14840,1423],{"emptyLinePlaceholder":234},[1397,14842,14843,14845],{"class":1399,"line":1687},[1397,14844,10245],{"class":1407},[1397,14846,14847],{"class":1403}," formatted\n",[1397,14849,14850],{"class":1399,"line":2275},[1397,14851,2023],{"class":1403},[1397,14853,14854],{"class":1399,"line":2281},[1397,14855,1690],{"class":1403},[211,14857,14858],{},"String accountFormat this adds spaces to get the usual display of 1234 56 78903. I find substring handling in swift to be a bit painful since you can't index into a string just with numbers - you have to use String.Index - so this extension takes care of that by defining an additional \"sub\" extension method returning a substring. I'm sure there are cleaner ways to do this but it does at least works:",[1317,14860,14862],{"className":8610,"code":14861,"language":8332,"meta":227,"style":227},"extension String {\n    func sub(_ start: Int, _ count: Int) -> String{\n        return String(self[self.index(self.startIndex, offsetBy: start)..\u003Cself.index(self.startIndex, offsetBy: start + count)])\n    }\n\n    func accountFormat() -> String {\n        if (self.count == 11)   {\n            return String(\"\\(self.sub(0, 4)) \\(self.sub(4, 2)) \\(self.sub(6, 5))\")\n        } else {\n            return self\n        }\n    }\n}\n",[1296,14863,14864,14872,14905,14970,14974,14978,14993,15014,15088,15096,15103,15107,15111],{"__ignoreMap":227},[1397,14865,14866,14868,14870],{"class":1399,"line":1400},[1397,14867,10063],{"class":1407},[1397,14869,10367],{"class":1561},[1397,14871,2493],{"class":1403},[1397,14873,14874,14876,14879,14881,14883,14886,14888,14890,14892,14895,14897,14899,14901,14903],{"class":1399,"line":228},[1397,14875,12757],{"class":1407},[1397,14877,14878],{"class":1414}," sub",[1397,14880,1435],{"class":1403},[1397,14882,9675],{"class":1414},[1397,14884,14885],{"class":1403}," start: ",[1397,14887,5353],{"class":1561},[1397,14889,1442],{"class":1403},[1397,14891,9675],{"class":1414},[1397,14893,14894],{"class":1403}," count: ",[1397,14896,5353],{"class":1561},[1397,14898,3991],{"class":1403},[1397,14900,10091],{"class":1407},[1397,14902,10367],{"class":1561},[1397,14904,8551],{"class":1403},[1397,14906,14907,14909,14911,14913,14915,14917,14919,14921,14924,14926,14928,14930,14933,14935,14938,14941,14944,14946,14948,14950,14952,14954,14956,14958,14960,14962,14965,14967],{"class":1399,"line":1426},[1397,14908,10245],{"class":1407},[1397,14910,10367],{"class":1561},[1397,14912,1435],{"class":1403},[1397,14914,9222],{"class":1561},[1397,14916,2519],{"class":1403},[1397,14918,9222],{"class":1561},[1397,14920,1133],{"class":1403},[1397,14922,14923],{"class":1561},"index",[1397,14925,1435],{"class":1403},[1397,14927,9222],{"class":1561},[1397,14929,1133],{"class":1403},[1397,14931,14932],{"class":1561},"startIndex",[1397,14934,1442],{"class":1403},[1397,14936,14937],{"class":1561},"offsetBy",[1397,14939,14940],{"class":1403},": start)",[1397,14942,14943],{"class":1407},"..\u003C",[1397,14945,9222],{"class":1561},[1397,14947,1133],{"class":1403},[1397,14949,14923],{"class":1561},[1397,14951,1435],{"class":1403},[1397,14953,9222],{"class":1561},[1397,14955,1133],{"class":1403},[1397,14957,14932],{"class":1561},[1397,14959,1442],{"class":1403},[1397,14961,14937],{"class":1561},[1397,14963,14964],{"class":1403},": start ",[1397,14966,1639],{"class":1407},[1397,14968,14969],{"class":1403}," count)])\n",[1397,14971,14972],{"class":1399,"line":1451},[1397,14973,2023],{"class":1403},[1397,14975,14976],{"class":1399,"line":1470},[1397,14977,1423],{"emptyLinePlaceholder":234},[1397,14979,14980,14982,14985,14987,14989,14991],{"class":1399,"line":1489},[1397,14981,12757],{"class":1407},[1397,14983,14984],{"class":1414}," accountFormat",[1397,14986,10433],{"class":1403},[1397,14988,10091],{"class":1407},[1397,14990,10367],{"class":1561},[1397,14992,2493],{"class":1403},[1397,14994,14995,14997,14999,15001,15003,15006,15008,15011],{"class":1399,"line":1651},[1397,14996,10103],{"class":1407},[1397,14998,1550],{"class":1403},[1397,15000,9222],{"class":1561},[1397,15002,1133],{"class":1403},[1397,15004,15005],{"class":1561},"count",[1397,15007,5869],{"class":1407},[1397,15009,15010],{"class":1561}," 11",[1397,15012,15013],{"class":1403},")   {\n",[1397,15015,15016,15018,15020,15022,15024,15026,15028,15030,15033,15035,15037,15039,15042,15045,15048,15050,15052,15054,15056,15058,15060,15063,15065,15067,15069,15071,15073,15075,15078,15080,15082,15084,15086],{"class":1399,"line":1675},[1397,15017,14819],{"class":1407},[1397,15019,10367],{"class":1561},[1397,15021,1435],{"class":1403},[1397,15023,3420],{"class":1438},[1397,15025,14825],{"class":1438},[1397,15027,9222],{"class":1561},[1397,15029,1133],{"class":1438},[1397,15031,15032],{"class":1561},"sub",[1397,15034,1435],{"class":1438},[1397,15036,6161],{"class":1561},[1397,15038,1442],{"class":1438},[1397,15040,15041],{"class":1561},"4",[1397,15043,15044],{"class":1438},"))",[1397,15046,15047],{"class":1438}," \\(",[1397,15049,9222],{"class":1561},[1397,15051,1133],{"class":1438},[1397,15053,15032],{"class":1561},[1397,15055,1435],{"class":1438},[1397,15057,15041],{"class":1561},[1397,15059,1442],{"class":1438},[1397,15061,15062],{"class":1561},"2",[1397,15064,15044],{"class":1438},[1397,15066,15047],{"class":1438},[1397,15068,9222],{"class":1561},[1397,15070,1133],{"class":1438},[1397,15072,15032],{"class":1561},[1397,15074,1435],{"class":1438},[1397,15076,15077],{"class":1561},"6",[1397,15079,1442],{"class":1438},[1397,15081,2714],{"class":1561},[1397,15083,15044],{"class":1438},[1397,15085,3420],{"class":1438},[1397,15087,2549],{"class":1403},[1397,15089,15090,15092,15094],{"class":1399,"line":1687},[1397,15091,10212],{"class":1403},[1397,15093,10184],{"class":1407},[1397,15095,2493],{"class":1403},[1397,15097,15098,15100],{"class":1399,"line":2275},[1397,15099,14819],{"class":1407},[1397,15101,15102],{"class":1561}," self\n",[1397,15104,15105],{"class":1399,"line":2281},[1397,15106,3427],{"class":1403},[1397,15108,15109],{"class":1399,"line":2540},[1397,15110,2023],{"class":1403},[1397,15112,15113],{"class":1399,"line":2552},[1397,15114,1690],{"class":1403},[8206,15116],{},[1254,15118,2061],{"id":2060},[211,15120,15121],{},"At this point - we are able to start the app - scan a config if needed, authenticate with biometrics (face or touch ID) and now show the account name, number and balance\u002Favailable values.",[211,15123,15124],{},"The next step will be to show the last 10 transactions and then we will have the same functions that I had in the previous storyboard based app.",[8206,15126],{},[211,15128,15129],{},[1118,15130,8315],{"href":8313,"rel":15131,"target":1125},[1122,1123,1124],[2066,15133,15134],{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s6RL2, html code.shiki .s6RL2{--shiki-default:#FDAEB7;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":227,"searchDepth":228,"depth":228,"links":15136},[15137,15141,15142,15146],{"id":12922,"depth":228,"text":12923,"children":15138},[15139,15140],{"id":8532,"depth":1426,"text":8533},{"id":13355,"depth":1426,"text":13356},{"id":13737,"depth":228,"text":13738},{"id":14031,"depth":228,"text":14032,"children":15143},[15144,15145],{"id":14046,"depth":1426,"text":14047},{"id":14476,"depth":1426,"text":12909},{"id":2060,"depth":228,"text":2061},"2020-03-30 20:50 +0200","The app so far has the ability to get an authentication token. The next step is to actually use it.",{},"\u002F2020\u002F03\u002F30\u002Faccount-view",{"title":12909,"description":12914},{"loc":15150},"2020\u002F03\u002F30\u002Faccount-view",[8331,8332,8333,8334,8329],"SsFTx5i9jFONGy1f48mBTdCn6NKQa3wvG1ndCVddTnw",{"id":15157,"title":15158,"body":15159,"category":231,"date":15516,"description":15163,"embedImage":231,"extension":232,"image":231,"intro":15163,"meta":15517,"navigation":234,"path":15518,"seo":15519,"series":8325,"sitemap":15520,"stem":15521,"tags":15522,"__hash__":15523},"content\u002F2020\u002F04\u002F01\u002Fobservable-account.md","Observable account",{"type":208,"value":15160,"toc":15510},[15161,15164,15167,15169,15173,15176,15179,15181,15185,15188,15205,15208,15246,15249,15279,15282,15297,15299,15305,15308,15314,15317,15381,15384,15480,15486,15493,15495,15497,15500,15502,15507],[211,15162,15163],{},"So far we are showing the account balances - and the next step is really to sort out the transaction view.",[211,15165,15166],{},"But - before we do that - let's sort out the nested callbacks we currently have.",[8206,15168],{},[1254,15170,15172],{"id":15171},"callbacks","Callbacks",[211,15174,15175],{},"These work - but - nesting them makes the code harder to follow.",[211,15177,15178],{},"Let's make the account handling use the built in ObservableObject handling - so that when the account changes the view notices this. We can then write a simplified refresh method that will also allow us to refresh the transactions when we add them.",[8206,15180],{},[1254,15182,15184],{"id":15183},"make-account-service-observable","Make Account Service observable",[211,15186,15187],{},"We need to make some changes to the account service.",[444,15189,15190,15196,15202],{},[447,15191,15192,15193],{},"it needs to implement ",[1296,15194,15195],{},"ObservableObject",[447,15197,15198,15199],{},"it needs to have a variable that is annotated as ",[1296,15200,15201],{},"@Published",[447,15203,15204],{},"the method is no longer static, no longer gets callbacks (and we'll rename it refresh)",[211,15206,15207],{},"Change the class definition to start:",[1317,15209,15211],{"className":8610,"code":15210,"language":8332,"meta":227,"style":227},"class AccountService : ObservableObject {\n    @Published public var account : Account? = nil\n",[1296,15212,15213,15227],{"__ignoreMap":227},[1397,15214,15215,15217,15220,15222,15225],{"class":1399,"line":1400},[1397,15216,8618],{"class":1407},[1397,15218,15219],{"class":1414}," AccountService",[1397,15221,8623],{"class":1403},[1397,15223,15224],{"class":1414},"ObservableObject ",[1397,15226,8551],{"class":1403},[1397,15228,15229,15232,15235,15237,15240,15242,15244],{"class":1399,"line":228},[1397,15230,15231],{"class":1407},"    @Published",[1397,15233,15234],{"class":1407}," public",[1397,15236,8842],{"class":1407},[1397,15238,15239],{"class":1403}," account : Account",[1397,15241,10096],{"class":1407},[1397,15243,2215],{"class":1407},[1397,15245,10248],{"class":1561},[211,15247,15248],{},"Change the method signature to:",[1317,15250,15252],{"className":8610,"code":15251,"language":8332,"meta":227,"style":227},"public func refresh(token: String, config: Config)\n",[1296,15253,15254],{"__ignoreMap":227},[1397,15255,15256,15259,15261,15264,15266,15268,15270,15272,15274,15276],{"class":1399,"line":1400},[1397,15257,15258],{"class":1407},"public",[1397,15260,10075],{"class":1407},[1397,15262,15263],{"class":1414}," refresh",[1397,15265,1435],{"class":1403},[1397,15267,13384],{"class":1414},[1397,15269,2513],{"class":1403},[1397,15271,1520],{"class":1561},[1397,15273,1442],{"class":1403},[1397,15275,3984],{"class":1414},[1397,15277,15278],{"class":1403},": Config)\n",[211,15280,15281],{},"Remove all calls to callback in the method and at the end if we made it that far we set:",[1317,15283,15285],{"className":8610,"code":15284,"language":8332,"meta":227,"style":227},"self.account = account\n",[1296,15286,15287],{"__ignoreMap":227},[1397,15288,15289,15291,15293,15295],{"class":1399,"line":1400},[1397,15290,9222],{"class":1561},[1397,15292,13954],{"class":1403},[1397,15294,1408],{"class":1407},[1397,15296,13959],{"class":1403},[1254,15298,14047],{"id":14046},[211,15300,15301,15302,15304],{},"We now want to listen to changes to the service - the view will be notified that a change has happened when the ",[1296,15303,15201],{}," variable changes.",[211,15306,15307],{},"In the content view remove the state variable for account and add",[1317,15309,15312],{"className":15310,"code":15311,"language":1322},[1320],"@ObservedObject var accountService = AccountService()\n",[1296,15313,15311],{"__ignoreMap":227},[211,15315,15316],{},"In the view heirarchy - where we have the AccountView - let's use the service variable when present:",[1317,15318,15320],{"className":8610,"code":15319,"language":8332,"meta":227,"style":227},"if (accountService.account != nil) {\n    AccountView(account: accountService.account!)\n        .navigationBarTitle(Text(accountService.account!.name), displayMode: .inline)\n    }\n}\n",[1296,15321,15322,15335,15351,15373,15377],{"__ignoreMap":227},[1397,15323,15324,15326,15329,15331,15333],{"class":1399,"line":1400},[1397,15325,9150],{"class":1407},[1397,15327,15328],{"class":1403}," (accountService.account ",[1397,15330,10803],{"class":1407},[1397,15332,10806],{"class":1561},[1397,15334,1580],{"class":1403},[1397,15336,15337,15340,15342,15344,15347,15349],{"class":1399,"line":228},[1397,15338,15339],{"class":1561},"    AccountView",[1397,15341,1435],{"class":1403},[1397,15343,14163],{"class":1561},[1397,15345,15346],{"class":1403},": accountService.account",[1397,15348,10819],{"class":1407},[1397,15350,2549],{"class":1403},[1397,15352,15353,15355,15357,15359,15361,15364,15366,15369,15371],{"class":1399,"line":1426},[1397,15354,8918],{"class":1403},[1397,15356,14179],{"class":1561},[1397,15358,1435],{"class":1403},[1397,15360,14184],{"class":1561},[1397,15362,15363],{"class":1403},"(accountService.account",[1397,15365,10819],{"class":1407},[1397,15367,15368],{"class":1403},".name), ",[1397,15370,14190],{"class":1561},[1397,15372,14193],{"class":1403},[1397,15374,15375],{"class":1399,"line":1451},[1397,15376,2023],{"class":1403},[1397,15378,15379],{"class":1399,"line":1470},[1397,15380,1690],{"class":1403},[211,15382,15383],{},"Replace the entire getToken method (which since the last change is badly named too) with a refresh method:",[1317,15385,15387],{"className":8610,"code":15386,"language":8332,"meta":227,"style":227},"func refresh() {\n    if let config = self.config {\n        TokenService.getToken(config: config) { (accessToken) in\n            if let token = accessToken {\n                self.accountService.refresh(token: token, config: config)\n                \u002F\u002F We will add transaction refresh here later on\n            }\n        }\n    }\n}\n",[1296,15388,15389,15397,15411,15425,15437,15459,15464,15468,15472,15476],{"__ignoreMap":227},[1397,15390,15391,15393,15395],{"class":1399,"line":1400},[1397,15392,9667],{"class":1407},[1397,15394,15263],{"class":1414},[1397,15396,10293],{"class":1403},[1397,15398,15399,15401,15403,15405,15407,15409],{"class":1399,"line":228},[1397,15400,9200],{"class":1407},[1397,15402,9153],{"class":1407},[1397,15404,9205],{"class":1403},[1397,15406,1408],{"class":1407},[1397,15408,11529],{"class":1561},[1397,15410,12776],{"class":1403},[1397,15412,15413,15415,15417,15419,15421,15423],{"class":1399,"line":1426},[1397,15414,13888],{"class":1403},[1397,15416,12784],{"class":1561},[1397,15418,1435],{"class":1403},[1397,15420,3984],{"class":1561},[1397,15422,12791],{"class":1403},[1397,15424,9022],{"class":1407},[1397,15426,15427,15429,15431,15433,15435],{"class":1399,"line":1451},[1397,15428,10146],{"class":1407},[1397,15430,9153],{"class":1407},[1397,15432,12650],{"class":1403},[1397,15434,1408],{"class":1407},[1397,15436,13911],{"class":1403},[1397,15438,15439,15441,15444,15447,15449,15451,15454,15456],{"class":1399,"line":1470},[1397,15440,9997],{"class":1561},[1397,15442,15443],{"class":1403},".accountService.",[1397,15445,15446],{"class":1561},"refresh",[1397,15448,1435],{"class":1403},[1397,15450,13384],{"class":1561},[1397,15452,15453],{"class":1403},": token, ",[1397,15455,3984],{"class":1561},[1397,15457,15458],{"class":1403},": config)\n",[1397,15460,15461],{"class":1399,"line":1489},[1397,15462,15463],{"class":3619},"                \u002F\u002F We will add transaction refresh here later on\n",[1397,15465,15466],{"class":1399,"line":1651},[1397,15467,10008],{"class":1403},[1397,15469,15470],{"class":1399,"line":1675},[1397,15471,3427],{"class":1403},[1397,15473,15474],{"class":1399,"line":1687},[1397,15475,2023],{"class":1403},[1397,15477,15478],{"class":1399,"line":2275},[1397,15479,1690],{"class":1403},[211,15481,15482,15483],{},"In the .OK state of askForAuth - call ",[1296,15484,15485],{},"self.refresh()",[211,15487,15488,15489,15492],{},"If you want you can also add another naviagtionBarItem with a suitable icon (",[1296,15490,15491],{},"Image(systemName: \"arrow.clockwise\")"," perhaps) that calls refresh().",[8206,15494],{},[1254,15496,2061],{"id":2060},[211,15498,15499],{},"We've updated the app so that the view stack watches for changes to the account. This will make it easier to add the transaction views later on.",[8206,15501],{},[211,15503,15504],{},[1118,15505,8315],{"href":8313,"rel":15506,"target":1125},[1122,1123,1124],[2066,15508,15509],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":227,"searchDepth":228,"depth":228,"links":15511},[15512,15513,15514,15515],{"id":15171,"depth":228,"text":15172},{"id":15183,"depth":228,"text":15184},{"id":14046,"depth":228,"text":14047},{"id":2060,"depth":228,"text":2061},"2020-04-01 08:15 +0200",{},"\u002F2020\u002F04\u002F01\u002Fobservable-account",{"title":15158,"description":15163},{"loc":15518},"2020\u002F04\u002F01\u002Fobservable-account",[8331,8332,8333,8334,8329],"-P3Vy5zEuF_e3DkMmeWRzERCM8Hl6qaj5jCiP5-DHss",191,1775292986372]